Tracks
/
Factor
Factor
/
Übungen
/
Lasagne-Koryphäe
Lasagne-Koryphäe

Lasagne-Koryphäe

Lernübung

Einführung

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.

Zwischenwerte mit :> binden

Innerhalb 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.

Lambdas: [| 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.

Wann du lokale Variablen verwenden solltest

Greife zu lokalen Variablen, wenn:

  • Derselbe Eingabewert wird an mehr als einer Stelle verwendet.
  • Die natürliche Reihenfolge der Operationen passt nicht zur Stack-Reihenfolge, und die dup/swap/rot-Korrektur ist schwer zu lesen.
  • Du befindest dich in einem Inline-Lambda, das an ein Wort höherer Ordnung übergeben wird, das dir mehrere Werte auf einmal liefert (wie bei 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 anderen

assoc-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 betrachtet

Wenn 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.

Anleitung

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.

1. Bestimme den Garstatus

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."
  • jede andere Zahl → "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."

2. Schätze die Vorbereitungszeit

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

3. Berechne die benötigten Mengen an Nudeln und Soße

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

4. Füge die geheime Zutat hinzu

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" }

5. Skaliere das Rezept

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 } }
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Factor Exercism

Bereit, mit Lasagne-Koryphäe zu starten?

Melde dich bei Exercism an, um Factor mit 47 Konzepte163 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.