Ich werde ein bisschen erklären, wie es intern funktioniert. Zunächst müssen Sie erkennen, dass Haskell für seine Werte ein sogenanntes Thunk verwendet. Ein Thunk ist im Grunde ein Wert, der noch nicht berechnet wurde - stellen Sie sich das als Funktion von 0 Argumenten vor. Wann immer Haskell möchte, kann er den Thunk bewerten (oder teilweise bewerten) und ihn in einen realen Wert umwandeln. Wenn ein Thunk nur teilweise ausgewertet wird, enthält der resultierende Wert mehr Thunks.
Betrachten Sie zum Beispiel den Ausdruck:
(2 + 3, 4)
In einer gewöhnlichen Sprache würde dieser Wert als gespeichert (5, 4)
, in Haskell jedoch als (<thunk 2 + 3>, 4)
. Wenn Sie nach dem zweiten Element dieses Tupels fragen, wird "4" angezeigt, ohne dass jemals 2 und 3 addiert werden. Nur wenn Sie nach dem ersten Element dieses Tupels fragen, bewertet es den Thunk und stellt fest, dass es 5 ist.
Mit Fibs ist es etwas komplizierter, weil es rekursiv ist, aber wir können die gleiche Idee verwenden. Da fibs
Haskell keine Argumente akzeptiert, werden alle entdeckten Listenelemente dauerhaft gespeichert - das ist wichtig.
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Es hilft Haskells aktuelles Wissen von drei Ausdrücke sichtbar zu machen: fibs
, tail fibs
und zipWith (+) fibs (tail fibs)
. Wir gehen davon aus, dass Haskell zunächst Folgendes weiß:
fibs = 0 : 1 : <thunk1>
tail fibs = 1 : <thunk1>
zipWith (+) fibs (tail fibs) = <thunk1>
Beachten Sie, dass die 2. Reihe nur die erste nach links verschobene ist und die 3. Reihe die ersten beiden summierten Reihen.
Fragen Sie nach take 2 fibs
und Sie werden bekommen [0, 1]
. Haskell muss das oben Gesagte nicht weiter auswerten, um dies herauszufinden.
Fragen Sie nach take 3 fibs
und Haskell wird die 0 und 1 erhalten und dann erkennen, dass es den Thunk teilweise bewerten muss . Um vollständig ausgewertet zu werden zipWith (+) fibs (tail fibs)
, müssen die ersten beiden Zeilen summiert werden - das kann es nicht vollständig, aber es kann beginnen , die ersten beiden Zeilen zu summieren:
fibs = 0 : 1 : 1: <thunk2>
tail fibs = 1 : 1 : <thunk2>
zipWith (+) fibs (tail fibs) = 1 : <thunk2>
Beachten Sie, dass ich die "1" in der 3. Zeile ausgefüllt habe und sie automatisch auch in der ersten und zweiten Zeile angezeigt wurde, da alle drei Zeilen denselben Thunk verwenden (stellen Sie sich das wie einen Zeiger vor, auf den geschrieben wurde). Und weil die Auswertung noch nicht abgeschlossen war, wurde ein neuer Thunk erstellt, der den Rest der Liste enthält, falls dies jemals benötigt werden sollte.
Es wird jedoch nicht benötigt, da take 3 fibs
es erledigt ist : [0, 1, 1]
. Aber jetzt sagen Sie, Sie fragen nach take 50 fibs
; Haskell erinnert sich bereits an die 0, 1 und 1. Aber es muss weitergehen. Also summiert es die ersten beiden Zeilen weiter:
fibs = 0 : 1 : 1 : 2 : <thunk3>
tail fibs = 1 : 1 : 2 : <thunk3>
zipWith (+) fibs (tail fibs) = 1 : 2 : <thunk3>
...
fibs = 0 : 1 : 1 : 2 : 3 : <thunk4>
tail fibs = 1 : 1 : 2 : 3 : <thunk4>
zipWith (+) fibs (tail fibs) = 1 : 2 : 3 : <thunk4>
Und so weiter, bis es 48 Spalten der 3. Zeile ausgefüllt und damit die ersten 50 Zahlen ausgearbeitet hat. Haskell wertet genau so viel aus, wie es braucht, und lässt den unendlichen "Rest" der Sequenz als Thunk-Objekt, falls es jemals mehr braucht.
Beachten Sie take 25 fibs
, dass Haskell es nicht erneut auswertet , wenn Sie später danach fragen - es werden nur die ersten 25 Zahlen aus der Liste berechnet, die es bereits berechnet hat.
Bearbeiten : Jedem Thunk wurde eine eindeutige Nummer hinzugefügt, um Verwirrung zu vermeiden.