Es ist in der Tat ein Ergebnis der Faulheit.
Faulheit bedeutet, dass das bloße Definieren eines Werts nicht bedeutet, dass es ausgewertet wird; Das wird nur passieren, wenn es für etwas benötigt wird. Wenn es nicht benötigt wird, tut der Code, der es tatsächlich produziert, nichts "tun". Wenn ein bestimmter Wert benötigt wird, wird der Code ausgeführt, aber nur das erste Mal, es wäre erforderlich; Wenn andere Verweise auf denselben Wert vorhanden sind und erneut verwendet werden, verwenden diese Verwendungen einfach den Wert, der beim ersten Mal erstellt wurde.
Sie müssen daran denken, dass Funktionen sind Werte in jeder Hinsicht des Begriffs; alles, was für gewöhnliche Werte gilt, gilt auch für Funktionen. Ihre Definition von f
schreibt einfach einen Ausdruck für einen Wert, die Auswertung des Ausdrucks wird zurückgestellt, bis der Wert f
tatsächlich benötigt wird, und da der doppelte Wert (Funktion) benötigt wird, wird der Ausdruck computes gespeichert und zum zweiten Mal wiederverwendet .
Ermöglicht es genauer aussehen:
f=trace("f was called")$(+1)
Sie definieren einen Wert f
mit einer einfachen Gleichung (keinen syntaktischen Zucker unter Verwendung für das Schreiben Argument auf der linken Seite der Gleichung oder die Bereitstellung Fälle über mehrere Gleichungen). Also können wir einfach die rechte Seite als einen einzelnen Ausdruck nehmen, der den Wert f
definiert. Gerade definieren es tut nichts, er sitzt dort, bis Sie anrufen:
print $ f 1
Jetzt drucken ihr Argument ausgewertet muss, so ist dies zwingt den Ausdruck f 1
. Aber wir können f
nicht auf 1
anwenden, ohne vorher f
zu erzwingen. Also müssen wir herausfinden, welche Funktion der Ausdruck trace "f was called" $ (+1)
auswertet. So trace
wird tatsächlich aufgerufen, wird seine unsichere IO-Druck und f was called
erscheint am Terminal, und dann trace
gibt sein zweites Argument zurück: (+1)
.
So jetzt wissen wir, welche Funktion f
ist: (+1)
. f
wird nun eine direkte Referenz auf diese Funktion sein, ohne den ursprünglichen Code trace("f was called")$(+1)
auszuwerten, wenn f
erneut aufgerufen wird. Deshalb macht das zweite print
nichts.
Dieser Fall ist ganz anders, obwohl es ähnlich aussehen könnte: durch Schreiben Argumente auf der linken Seite
f n=trace("f was called:"++show n)$n+1
Hier stellen wir sind den syntaktischen Zucker unter Verwendung von Funktionen für die Definition.Lassen Sie uns desugar dass Notation Lambda deutlicher, was der tatsächliche Wert um zu sehen, zu f
gebunden zu sein ist:
f = \n -> trace ("f was called:" ++ show n) $ n + 1
Hier haben wir einen Funktionswert geschrieben haben direkt, eher als ein Ausdruck, der ausgewertet werden können, um zur Folge eine Funktion. Also, wenn f
ausgewertet werden muss, bevor es auf 1
aufgerufen werden kann, ist der Wert von f
diese ganze Funktion; Der trace
Aufruf ist innerhalb die Funktion anstelle der Sache, die Ergebnis in einer Funktion aufgerufen wird. So wird trace
nicht als Teil der Bewertung f
aufgerufen, es wird als Teil der Auswertung der Anwendung f 1
aufgerufen. Wenn Sie das Ergebnis davon gespeichert haben (z. B. indem Sie let x = f 1
getan haben) und es dann mehrfach gedruckt haben, sehen Sie nur die eine Ablaufverfolgung. Aber wenn wir kommen f 2
, trace
Aufruf ist immer noch dort in der Funktion, die der Wert f
ist, so wenn f
heißt wieder so ist trace
.
Beachten Sie, dass "trace" den Typ "String -> a -> a" hat. Der Typ, der für "a" im Fall "f =" substituiert wird, unterscheidet sich von dem im Fall "f n =". Das sollte auch dazu beitragen, das unterschiedliche Verhalten zu erklären. –