Jongler avec la pile convient pour les mots courts. Quand un mot prend plusieurs entrées et que tu dois y faire référence par leur nom, Factor propose les locals, des liaisons nommées introduites avec :: (pour les définitions de mots) ou [| | ] (pour les lambdas en ligne).
:: ( inputs -- outputs )Remplace : par :: et les noms des entrées deviennent des variables locales dans le corps :
USING: locals ;
:: hypotenuse ( a b -- c )
a a *
b b *
+ sqrt ;
Compare avec la version qui manipule la pile :
! DOCTEST: SKIP (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
[ sq ] bi@ + sqrt ;
Les deux sont valides ; les locals brillent quand une entrée est utilisée à plusieurs endroits ou quand l'ordre des opérations ne suit pas naturellement l'ordre de la pile.
:>
À l'intérieur d'un corps :: (ou d'une lambda en ligne [| ... |]), :> dépile une valeur de la pile de données et la lie à une nouvelle variable locale :
value :> name ! immutable binding
value :> name! ! mutable binding (name! is its setter)
La forme immuable est la plus courante : utilise-la pour nommer une valeur intermédiaire afin que le corps se lise de haut en bas :
USING: locals math ;
:: average-3 ( x y z -- avg )
x y + z + :> sum
sum 3 / ;
La variante avec ! déclare la variable locale comme mutable ; plus loin dans le corps, value name! (sans deux-points, sans :>) la réaffecte :
USING: kernel locals math math.order ;
:: clamped ( x lo hi -- y )
x :> v!
v lo < [ lo v! ] when
v hi > [ hi v! ] when
v ;
Les locals mutables brillent à l'intérieur des mots d'itération que tu rencontreras dans les exercices suivants (par exemple while).
[let : une portée depuis n'importe où:> ne fonctionne qu'à l'intérieur d'une portée lexicale, que :: et [| ... |] créent automatiquement. Pour introduire des locals depuis un mot : ordinaire ou depuis le listener, enveloppe le code dans [let ... ] :
[let code :> name code :> name ... body ]
Chaque :> name lie la valeur au sommet de la pile de l'expression précédente ; les liaisons sont visibles depuis le reste de la forme.
USING: locals math ;
[let 3 :> x 4 :> y x x * y y * + ] . ! => 25
[let fait le pont entre les définitions : à la pile et les liaisons nommées : c'est pratique quand seule une partie d'un mot profite des locals.
[| inputs | body ]
À l'intérieur d'une quotation, [| inputs | body ] introduit des locals auxquels le corps peut faire référence par leur nom. C'est particulièrement utile avec les mots d'ordre supérieur comme assoc-map, dont la quotation reçoit key et value :
USING: assocs locals ;
H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }
La même forme fonctionne pour map-index (dans [sequences][sequences]), dont la quotation reçoit l'élément et son indice. Les noms indiquent clairement quel opérande joue quel rôle :
USING: locals sequences ;
! Divide each element by its 1-based position:
{ 10 80 270 } [| elt i | elt i 1 + / ] map-index .
! => { 10 40 90 }
L'effet sur la pile de la lambda est déterminé par la liste d'entrées et par ce que produit son corps.
Pense aux locals quand :
dup/swap/rot nécessaires pour corriger ça sont difficiles à lire.assoc-map).Quand le corps est un flux linéaire clair, les définitions : ordinaires restent plus courtes et tout aussi lisibles.
assoc-map : une entrée à la foisassoc-map (dans assocs) applique une quotation à chaque entrée d'une collection associative :
assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )
Une lambda avec locals est la façon la plus propre d'écrire la quotation.
cond
Quand tu dois aiguiller sur f ou sur une valeur numérique, cond est exactement la bonne forme : pas besoin de case, puisqu'il n'y a pas de valeur unique à laquelle comparer.
Te revoilà dans la cuisine, où tu affines la recette de lasagnes tirée de Les lasagnes succulentes de Leah. Les tâches qui suivent te demandent de jongler avec plusieurs entrées à la fois, et c'est exactement dans ces cas-là que les locals, les liaisons nommées de Factor, gagnent leur place.
Définis cooking-status pour qu'il récupère une lecture de minuterie sur la pile et renvoie une string indiquant l'état.
0 → "Lasagna is done."
f (la minuterie n'a jamais été réglée) → "You forgot to set the timer."
"Not done, please wait."
12 cooking-status . ! => "Not done, please wait."
0 cooking-status . ! => "Lasagna is done."
f cooking-status . ! => "You forgot to set the timer."
Définis preparation-time pour qu'il prenne un tableau de noms de couches et un nombre moyen de minutes par couche, et renvoie le temps de préparation total.
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18
Définis quantities pour qu'il prenne un tableau de noms de couches et renvoie deux valeurs : les grammes de nouilles nécessaires (50 g par couche de nouilles) et les litres de sauce nécessaires (1/5 litre par couche de sauce).
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5
Un ami t'envoie une liste d'ingrédients ; le dernier élément est son secret. Définis add-secret-ingredient pour qu'il prenne sa liste et la tienne, et renvoie ta liste avec le secret ajouté à la fin.
{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }
La recette du livre donne deux portions. Définis scale-recipe pour qu'il prenne une recette (une table de hachage associant le nom d'un ingrédient à une quantité) et un nombre de portions cible, et renvoie une nouvelle recette adaptée en conséquence.
H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
Inscris-toi sur Exercism pour apprendre et maîtriser Factor avec 47 concepts163 exercices, et un vrai mentorat humain, le tout gratuitement.