Comportement du @

Comportement du @

> Tutoriel LeekScript

Cette page est valable uniquement pour le LS1, en LS2 et plus il ne faut plus utiliser @

En Leekscript, si vous ne l'avez pas encore remarqué, il existe une limite d'opérations par tour qui dépend du nombre de coeurs du poireau. L'optimisation est donc une clé de la victoire. Cet article détaillera en profondeur le comportement du '@' qui est un outil très apprécié.

Son utilisation principale se rapproche des références, c'est-à-dire que les copies de valeur sont évitées.

Explication condensée

L'explication qui suit sert à donner une intuition du mécanisme derrière l’arobase. Elle ne constitue pas une description technique exacte car les sources de la version du LeekScript actuellement utilisée ne sont pas disponibles.

0; ""; []; function(x) { return x; };

Affectation

Lorsque vous créez un objet directement à l'aide d'un littéral (voir ci-dessus), il occupe de la place en mémoire. Pour pouvoir l'utiliser, il faut savoir où le trouver. Nous appellerons cette position l'adresse de l'objet. Lorsque vous assignez un objet à une variable, une copie de cet objet est réalisée, puis l'adresse est fournie à la variable. Au lieu de fournir l'adresse d'une copie, nous pouvons demander à ce que l'adresse de l'objet original soit utilisée, afin d'éviter une copie, en préfixant l'expression en main droite du signe égal d'un arobase. (main_gauche =@ main_droite)

var w = 0; // Une copie de 0 est réalisée et l'adresse de cette copie est fournie. var x =@ 0; // L'adresse de 0 est fournie. (Chaque littéral present dans le code est un objet différent.) var y = w; // Une copie de la copie de 0 est réalisée, et l'adresse de cette copie est fournie. var z =@ w; // L'adresse de la copie de 0 est fournie.

L'arobase est appliquée avec la précédence la plus élevée. Ainsi, x =@ y + z ne fera pas ce que vous auriez pu imaginer. L'expression équivalente est x = (@x) + y. Si vous souhaitez assigner l'adresse, il faudra ajouter des parentheses afin de spécifier à quoi l'arobase s'applique : x =@ (x + y).

Cas Particuliers

--- Les operations sur les nombres ne sont pas mutantes. Lorsque vous utilisez les opérateurs tel que +=, le résultat est un nouvel objet. Cela diffère des tableaux et des chaînes de caractères car ces deux dernières sont des structures et c'est leur contenu qui est modifié. Pour pouvoir comprendre un peu plus facilement comment cela fonctionne, il est utile de développer un peu les expressions. x += 1 devient x = x + 1, on constate qu'il y a réassignation. Tandis que x += [1] devient x[count(x)] = 1 ou bien push(x, 1)/pushAll(x, [1]) ; il est clair que la variable x n'est pas réaffectée.

Paramètre

De manière similaire, lorsque vous passez un objet en argument d'une fonction, une copie est réalisée et l'adresse de cette copie est fournie au paramètre. Cependant, contrairement à l'assignement à une variable, préfixer un paramètre d'un arobase à un comportement un peu plus complexe à l'application de la fonction :

Dans le rare cas où vous souhaiteriez passer une copie plutôt que l'adresse de la variable, vous pouvez préfixer l'argument d'un arobase : at(@zero).

En paramètre

Son utilisation la plus stable et pratique est dans les paramètres d'une fonction :

function with_a (@ param1 ) { } // Que nous allons confronter à : function whitout_a ( param1 ) { }

Remarque : Si vous modifiez le paramètre dans la fonction, cette modification aura aussi lieu dans la variable du programme appelant, car la valeur donnée est votre variable, pas la valeur de celle-ci, par exemple :

function add_1 (@param) { param += 1; }

var a = 5; add_1(a); debug (a); // affiche : '6'

Remarque : L'appel à une fonction coûte 1 opération. Ce coût a été déduit des résultats suivants.

Passage par une donnée brute Passage par le biais d'une variable Données passées en paramètre sans le '@' avec le '@' coût d'affectation à la variable (cf Affectation) sans le '@' avec le '@' 42 2 1 2 2 0 "a string" 2 1 2 2 0 function () {} 2 1 2 2 0 [42, -42, 42] 116 34 116 83 0 ["un autre string", -42, [42]] 154 45 154 110 0

On notera que pour réduire une affectation de quelques opérations, il est possible de faire a = @[42, -42, 42]; (*cf Affectation*) par exemple. Avec cette solution on retombe sur les mêmes résultats que d'envoyer les données brutes dans la fonction avec @ (appel à la fonction) soit 34 opérations pour l'affectation de [42, -42, 42], 45 opérations pour ["un autre string", -42, [42]] et 1 opération sur les 3 autres.

'''Conclusion'''

L'utilisation du '@' est plus que recommandé La création d'une variable pour un cas unique ne change rien (ni plus ni moins d'opérations) Sauf si vous souhaitez donner en paramètre une valeur brute à plusieurs reprises, dans ce cas créez des constantes => plus rentable À noter que le '@' est un passage par référence, i.e. le changement de la variable est répercuté dans le programme appelant cette fonction

Affectation

Dans cette seconde partie, nous allons approfondir l'utilité du '@' dans les affectations. Il s'utilise de cette façon :

a = @( 42 ); // avec 'a' une variable

Remarque : Les parenthèses peuvent être omises s'il s'agit d'un seul élément comme =@ 0 ou =@ variable mais pas =@ 1 + 1.

Passage par une donnée brute Passage par le biais d'une variable Données passées en paramètre sans le '@' avec le '@' sans le '@' avec le '@' 42 2 1 2 1 "a string" 2 1 2 1 function () {} 2 1 2 1 [42, -42, 42] 116 34 83 1 ["un autre string", -42, [42]] 154 45 110 1

Conclusion

Il est très conseillé d'utiliser le '@' pour vos affectations L'utilisation de variable sous forme de constante est plus optimisé Remarque : L'utilisation du '@' n'est pas sans risque, en effet dans certains il y a une "rétroaction", plus d'infos dans la prochaine partie

Rétroaction

J'appelle "rétroaction" le fait de modifier la valeur de la variable prise en référence lors d'une affectation. C'est exactement ce qui ce passe lors d'un passage par référence dans une fonction. Si l'on modifie le valeur dans la fonction, la variable passée en paramètre sera elle-même modifiée :

function add1 (@a) { a++; }

b = 0; add1(b); debug(b); // affiche : 1

Dans certains cas d'affectation, ce phénomène se produit aussi...

var b = DEFAULT_VALUE; var a = @b; a = OTHER_VALUE; // b = DEFAULT_VALUE ou OTHER_VALUE ?

Dans cet exemple, b contient la valeur DEFAULT_VALUE à la fin du programme. En fait, voilà ce qui se passe :

La "référence" entre 'a' et 'b' est brisé à cause du signe '='. En revanche pour garder cette référence il faut modifier 'a' et non le réaffecter.

Cependant cela ne marche pas pour tous les types. Voici les résultats de mes recherches :

Pour ceux qui n'ont jamais entendu parler de pointeurs, c'est une variable qui contient l'adresse mémoire d'une autre case mémoire. On peut se le représenter par une flèche (qui pointe donc vers une case mémoire)... En LeekScript, les tableaux et strings sont gérées avec ce système. Lorsque vous créez une variable comme ceci :

var tab = []; // ou var tab = @[];

Le programme alloue une case mémoire (ou un groupe) contenant [] puis fait pointer 'tab' sur cette case, 'tab' étant un nom donné à une case mémoire.

var tab1 = [1, 2, 3];

// 1) var tabCopy = tab1; // 2) var tabRef = @tab1;

Dans le premier cas, le tableau est copié, un nouvel espace est alloué et chaque valeur du tableau 'tab' est copié dans cet espace, la variable 'tabCopy' pointe vers ce nouvel espace. Alors que dans le second cas, la variable 'tabRef' pointe vers le même espace mémoire que 'tab', si 'tab' est modifié (pas affecté !), 'tabRef' le sera également. Voici comment sera représentée la mémoire pour notre code (grossièrement) :

!TabLS

Ainsi il est possible de "jouer" avec le '@' pour manipuler les tableaux. Dans le cas ou une tab contient un tableau de tableau, il est possible d'obtenir une référence vers ce second tableau avec le '@' :

var tab = [1, 2, [3, 4, 5]];

var secondTab = @tab[2]; debug(secondTab); // [3, 4, 5]

push(secondTab, 6); debug(tab); // [1, 2, [3, 4, 5, 6]]

push(tab[2], 7); debug(secondTab); // [3, 4, 5, 6, 7]

Remarque : Il n'est pas possible d'avoir une référence d'un tableau dans lui même. Cela crée une copie et les coûts en opérations avec.

var tab = [1, 2, 3];

tab[0] = @tab; // Créer une copie

Si vous utilisez les références vers les autres tableaux comme avec 'tabRef' en référence à 'tab', afin de ne pas briser cette référence, il faut modifier 'tabref' et non le réassigner...

// Chaque exemple est individuel et ne modifie pas les valeurs de départ // i.e. après chaque exemple, 'tabref' et 'tab' reprennent leurs valeurs par défaut, qui sont : var tab = [1, 2, 3]; var tabref = @tab;

// 1) tabRef = []; // ou tabref = @[];

// 2) tabRef += [4];

// 3) push(tabRef, 5);

// 4) remove(tabRef, 3);

tabref = [] et tab = [1, 2, 3]

tabref = [1, 2, 3, 4] et tab = [1, 2, 3, 4]

tabref = [1, 2, 3, 5] et tab = [1, 2, 3, 5]

tabref = [1, 2] et tab = [1, 2]

Conclusion

Avec l'affectation, le '@' ne s'utilise pas les yeux bandés Aucun risque lorsque la variable contient un nombre ou une fonction Une référence est créé lorsque la donnée est un tableau ou un string Cette référence est brisée si il y a réaffectation, pour la garder, il faut modifier l'objet avec par exemple '+=' ou 'push'

Return

Dans cette partie nous allons étudier l'utilisation du '@' pour les retours de fonctions. Son utilisation est très simple et dans la plupart des cas, la rétroaction ne vous embêtera pas, puisque la variable de retour est souvent créée dans la fonction. Considérons ce programme :

global _global = DEFAULT_VALUE;

function getGlobal () { return /* @ ? */ _global; }

var local = /* @ ? */ getGlobal();

/* Modification de local par exemple : push(local, 0); ou local += 1; Dépendant de DEFAULT_VALUE */

debug ("Local : " + local); debug ("Global : " + _global );

local = return ... local = return @ ... local = @ return ... local = @ return @ ... Valeur de _global Opérations Rétroaction Opérations Rétroaction Opérations Rétroaction Opérations Rétroaction 42 1 non 1 non 1 non 1 non "a string" 1 non 1 oui 1 oui 1 non function () {} 1 non 1 oui 1 oui 1 non [42, -42, 42] 82 non 1 oui 1 oui 83 non ["un autre string", -42, [42]] 109 non 1 oui 1 oui 110 non

Remarque : Le coût en opérations de l'affectation à local a été supprimé pour n'avoir que le coût du return

"@ return @ ..." signifie que un @ est placé à l'affectation, et un autre est placé avec le return.

Comme vous pouvez le voir, l’utilisation du '@' permet d'éviter de faire des copies inutiles, cependant la rétroaction s'effectue dans les mêmes cas que l'affectation (tableaux et strings). Il faut aussi remarquer que l'utilisation de deux '@' brise la référence car elle crée une copie. Afin de garder une cohérence, il faut soit renvoyer toujours avec 'return @...' soit penser à mettre un '@' lors de l'affectation du retour d'une fonction. Afin d'être plus malléable, il est conseillé de ne pas retourner la référence, car si l'utilisateur veut faire une copie, il y aura deux @, ce qui ajoutera une opération en plus du coût de la copie et ce n'est pas lisible.

Conclusion

Utiliser le '@' préserve une fois de plus les copies éventuelles Attention à ne pas mettre deux '@', ce qui provoque une copie et ajoute une opération Il est donc conseillé d'avoir une norme, en particulier ne pas retourner une référence

Conclusion

Voilà, j'ai fait le tour du '@'. Son comportement est très apprécié pour optimiser son code et gagner de précieuses opérations. On peut noter ces utilisations principales, ainsi que les techniques à adopter :

Le '@' ne fait pas tout, l'optimisation est essentielle dans la confection d'une IA mais c’est un bon début.