Per le word brevi va bene destreggiarsi con lo stack. Quando una word ha diversi input e devi riferirti a ciascuno per nome, Factor offre i locals: binding con nome introdotti con :: (per le definizioni di word) o [| | ] (per le lambda inline).
:: ( inputs -- outputs )Sostituisci : con :: e i nomi degli input diventano variabili locali nel corpo:
USING: locals ;
:: hypotenuse ( a b -- c )
a a *
b b *
+ sqrt ;
Confronta con la versione che destreggia lo stack:
! DOCTEST: SKIP (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
[ sq ] bi@ + sqrt ;
Entrambe sono valide: i locals danno il meglio quando a un input si fa riferimento più di una volta o quando l'ordine delle operazioni non coincide naturalmente con l'ordine dello stack.
:>
All'interno di un corpo :: (o di una lambda inline [| ... |]), :> estrae un valore dallo stack dati e lo associa a un nuovo local:
value :> name ! immutable binding
value :> name! ! mutable binding (name! is its setter)
La forma immutabile è quella più comune: usala per dare un nome a un valore intermedio, così il corpo si legge dall'alto verso il basso:
USING: locals math ;
:: average-3 ( x y z -- avg )
x y + z + :> sum
sum 3 / ;
La variante con ! dichiara il local come mutabile; più avanti nel corpo, value name! (senza i due punti, senza :>) lo riassegna:
USING: kernel locals math math.order ;
:: clamped ( x lo hi -- y )
x :> v!
v lo < [ lo v! ] when
v hi > [ hi v! ] when
v ;
I locals mutabili danno il meglio all'interno delle word di iterazione che incontrerai negli esercizi successivi (ad esempio while).
[let: uno scope da qualsiasi punto:> funziona solo all'interno di uno scope lessicale, che :: e [| ... |] creano automaticamente. Per introdurre locals da una word : semplice o dal listener, avvolgi il codice in [let ... ]:
[let code :> name code :> name ... body ]
Ogni :> name associa il valore in cima allo stack dell'espressione precedente; i binding sono visibili al resto della forma.
USING: locals math ;
[let 3 :> x 4 :> y x x * y y * + ] . ! => 25
[let è il ponte tra le definizioni : in stile stack e i binding con nome: torna utile quando solo una parte di una word trae vantaggio dai locals.
[| inputs | body ]
All'interno di una quotation, [| inputs | body ] introduce dei locals a cui il corpo può riferirsi per nome. È particolarmente utile con le word di ordine superiore come assoc-map, la cui quotation riceve sia key sia value:
USING: assocs locals ;
H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }
La stessa forma funziona con map-index (in [sequences][sequences]), la cui quotation riceve sia l'elemento sia il suo indice. I nomi chiariscono quale operando svolge quale ruolo:
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'effetto stack della lambda è determinato dalla lista degli input e da ciò che produce il suo corpo.
Ricorri ai locals quando:
dup/swap/rot necessario per sistemarlo è difficile da leggere.assoc-map).Quando il corpo è un flusso lineare e pulito, le definizioni : semplici restano più brevi e almeno altrettanto leggibili.
assoc-map: una voce alla voltaassoc-map (in assocs) applica una quotation a ogni voce di una collezione associativa:
assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )
Una lambda con locals è il modo più pulito per scrivere la quotation.
cond
Quando devi distinguere in base a f o a un valore numerico, cond è esattamente la forma giusta: non serve case, perché non c'è un singolo valore con cui confrontarsi.
Sei di nuovo in cucina, a rifinire la ricetta delle lasagne da Le deliziose lasagne di Leah. Le attività di questo esercizio ti chiederanno di gestire più input contemporaneamente: ed è proprio in questi casi che le variabili locali, cioè i binding con nome di Factor, si rivelano preziose.
Definisci cooking-status in modo che prelevi una lettura del timer
dallo stack e restituisca una stringa di stato.
0 → "Lasagna is done."
f (il timer non è mai stato impostato) → "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."
Definisci preparation-time in modo che prenda un array di nomi di
strato e un numero medio di minuti per strato, e restituisca il tempo
di preparazione totale.
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18
Definisci quantities in modo che prenda un array di nomi di strato e
restituisca due valori: i grammi di noodles necessari (50 g per ogni
strato di noodles) e i litri di sugo necessari (1/5 di litro per ogni
strato di sugo).
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5
Una persona amica ti manda un elenco di ingredienti: l'ultimo
elemento è il suo ingrediente segreto. Definisci add-secret-ingredient
in modo che prenda il suo elenco e il tuo e restituisca il tuo elenco
con l'ingrediente segreto aggiunto in fondo.
{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }
La ricetta del libro di cucina produce due porzioni. Definisci
scale-recipe in modo che prenda una ricetta (una hashtable che associa
il nome di un ingrediente alla sua quantità) e un numero di porzioni
obiettivo, e restituisca una nuova ricetta scalata di conseguenza.
H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
Iscriviti a Exercism per imparare e padroneggiare Factor con 47 concetti163 esercizi e il mentoring di persone reali, tutto gratis.