Stack-Jonglieren ist für kurze Wörter in Ordnung. Wenn ein Wort mehrere
Eingabewerte hat und du sie mit Namen ansprechen musst, bietet Factor
lokale Variablen: benannte Bindungen, die mit :: (für
Wortdefinitionen) oder [| | ] (für Inline-Lambdas) eingeführt werden.
:: ( inputs -- outputs )Ersetze : durch ::, und die Namen der Eingabewerte werden im Rumpf
zu lokalen Variablen:
USING: locals ;
:: hypotenuse ( a b -- c )
a a *
b b *
+ sqrt ;
Vergleiche mit der Version, die den Stack umsortiert:
! DOCTEST: SKIP (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
[ sq ] bi@ + sqrt ;
Beide sind gültig: Lokale Variablen sind besonders nützlich, wenn ein Eingabewert mehr als einmal referenziert wird oder wenn die Reihenfolge der Operationen nicht natürlich zur Stack-Reihenfolge passt.
:> bindenInnerhalb eines ::-Rumpfs (oder eines Inline-Lambdas [| ... |])
nimmt :> einen Wert vom Datenstack und bindet ihn an eine neue lokale
Variable:
value :> name ! immutable binding
value :> name! ! mutable binding (name! is its setter)
Die unveränderliche Form ist die übliche: Verwende sie, um einen Zwischenwert zu benennen, damit sich der Rumpf von oben nach unten liest:
USING: locals math ;
:: average-3 ( x y z -- avg )
x y + z + :> sum
sum 3 / ;
Die !-Variante deklariert die lokale Variable als veränderlich;
später im Rumpf weist value name! (kein Doppelpunkt, kein :>)
ihr einen neuen Wert zu:
USING: kernel locals math math.order ;
:: clamped ( x lo hi -- y )
x :> v!
v lo < [ lo v! ] when
v hi > [ hi v! ] when
v ;
Veränderliche lokale Variablen sind in den Iterationswörtern von
Vorteil, die dir in späteren Übungen begegnen werden (z. B. while).
[let: ein Gültigkeitsbereich von überall:> funktioniert nur innerhalb eines lexikalischen
Gültigkeitsbereichs, den :: und [| ... |] automatisch erzeugen. Um
lokale Variablen aus einem einfachen :-Wort oder am Listener
einzuführen, schließe den Code in [let ... ] ein:
[let code :> name code :> name ... body ]
Jedes :> name bindet den obersten Stackwert des vorherigen
Ausdrucks; die Bindungen sind für den Rest der Form sichtbar.
USING: locals math ;
[let 3 :> x 4 :> y x x * y y * + ] . ! => 25
[let ist die Brücke zwischen Stack-orientierten :-Definitionen und
benannten Bindungen: praktisch, wenn nur ein Teil eines Wortes von
lokalen Variablen profitiert.
[| inputs | body ]
Innerhalb einer Quotation führt [| inputs | body ] lokale Variablen
ein, auf die der Rumpf mit Namen zugreifen kann. Das ist besonders
nützlich bei Wörtern höherer Ordnung wie assoc-map, deren Quotation
key und value erhält:
USING: assocs locals ;
H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }
Dieselbe Form funktioniert für map-index (in [sequences][sequences]),
deren Quotation das Element und seinen Index erhält. Die Namen
machen klar, welcher Operand welche Rolle spielt:
USING: locals sequences ;
! Divide each element by its 1-based position:
{ 10 80 270 } [| elt i | elt i 1 + / ] map-index .
! => { 10 40 90 }
Der Stack-Effekt des Lambdas wird durch die Eingabeliste und das bestimmt, was sein Rumpf produziert.
Greife zu lokalen Variablen, wenn:
dup/swap/rot-Korrektur ist schwer
zu lesen.assoc-map).Wenn der Rumpf ein sauberer linearer Ablauf ist, bleiben einfache
:-Definitionen kürzer und mindestens genauso lesbar.
assoc-map: ein Eintrag nach dem anderenassoc-map (in assocs) bildet eine Quotation über jeden
Eintrag einer assoziativen Sammlung ab:
assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )
Ein Lambda mit lokalen Variablen ist die sauberste Art, die Quotation zu schreiben.
cond erneut betrachtetWenn du auf f oder auf einen numerischen Wert verzweigen musst,
ist cond genau die richtige Form: kein case nötig, weil es keinen
einzelnen Wert gibt, mit dem verglichen werden kann.
Du bist zurück in der Küche und feilst am Lasagne-Rezept aus Leahs köstliche Lasagne. Bei den Aufgaben hier jonglierst du gleichzeitig mit mehreren Eingabewerten, und genau in solchen Fällen zahlen sich lokale Variablen aus, also die benannten Bindungen von Factor.
Definiere cooking-status, sodass es einen Timer-Wert vom Stack nimmt
und einen Status-String zurückgibt.
0 → "Lasagna is done."
f (der Timer wurde nie gestellt) → "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."
Definiere preparation-time, sodass es ein Array mit Schichtnamen und
eine durchschnittliche Minutenzahl pro Schicht nimmt und die gesamte
Vorbereitungszeit zurückgibt.
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18
Definiere quantities, sodass es ein Array mit Schichtnamen nimmt und
zwei Werte zurückgibt: die benötigte Menge Nudeln in Gramm (50 g pro
Nudelschicht) und die benötigte Menge Soße in Litern (1/5 Liter pro
Soßenschicht).
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5
Dein Freund schickt dir eine Liste mit Zutaten; der letzte Eintrag ist
sein Geheimnis. Definiere add-secret-ingredient, sodass es seine Liste
und deine nimmt und deine Liste mit der angehängten geheimen Zutat
zurückgibt.
{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }
Das Rezept im Kochbuch ergibt zwei Portionen. Definiere scale-recipe,
sodass es ein Rezept (eine Hashtable von Zutatenname zu Menge) und eine
Zielanzahl an Portionen nimmt und ein neues, passend skaliertes Rezept
zurückgibt.
H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
Melde dich bei Exercism an, um Factor mit 47 Konzepte163 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.