Hier ist eine kleine Tabelle, die nützlich ist, wenn Sie Code schreiben, der eine Sequenz mithilfe der "profanen" Rekursion (mithilfe des Stapels) oder mithilfe recur
(mithilfe der schwanzrekursiven Optimierung, also der eigentlichen Schleife) durchläuft .
Beachten Sie die Unterschiede im Verhalten von rest
und next
. In Kombination seq
damit ergibt sich die folgende Redewendung, bei der das Ende der Liste über seq
und der Rest der Liste über rest
(angepasst aus "The Joy of Clojure") getestet wird :
(defn print-seq [s]
(when (seq s)
(assert (and (not (nil? s)) (empty? s)))
(prn (first s))
(recur (rest s))))
Warum ist next
eifriger als rest
?
Wenn (next coll)
ausgewertet wird, kann das Ergebnis sein nil
. Dies muss sofort bekannt sein (dh nil
muss tatsächlich zurückgegeben werden), da der Anrufer basierend auf der Wahrheit von verzweigen kann nil
.
Wenn (rest coll)
ausgewertet wird, kann das Ergebnis nicht sein nil
. Wenn der Aufrufer das Ergebnis dann nicht mithilfe eines Funktionsaufrufs auf Leere testet, kann die Erzeugung eines "nächsten Elements" in einer Lazy-Seq auf die tatsächlich benötigte Zeit verzögert werden.
Beispiel
Eine völlig faule Sammlung, alle Berechnungen werden "bis zur Verwendung angehalten".
(def x
(lazy-seq
(println "first lazy-seq evaluated")
(cons 1
(lazy-seq
(println "second lazy-seq evaluated")
(cons 2
(lazy-seq
(println "third lazy-seq evaluated")))))))
Die Berechnung von "x" wird nun beim ersten "Lazy-Seq" ausgesetzt.
Mit dem eifrigen nächsten sehen wir zwei Bewertungen:
(def y (next x))
(type y)
(first y)
- Die erste Lazy-Seq wird ausgewertet, was zum Ausdruck führt
first lazy-seq evaluated
- Dies führt zu einer nicht leeren Struktur: einem Nachteil mit 1 links und einem Lazy-Seq rechts.
next
muss möglicherweise zurückkehren, nil
wenn der rechte Zweig leer ist. Wir müssen also eine Ebene tiefer prüfen.
- Die zweite Lazy-Seq wird ausgewertet, was zum Ausdruck führt
second lazy-seq evaluated
- Dies führt zu einer nicht leeren Struktur: einem Nachteil mit 2 links und einem Lazy-Seq rechts.
- Also nicht zurückkehren
nil
, sondern die Nachteile zurückgeben.
- Wenn Sie das
first
von erhalten y
, gibt es nichts zu tun, außer 2 von den bereits erhaltenen Nachteilen abzurufen.
Bei Verwendung des Lazer sehen rest
wir eine Bewertung (beachten Sie, dass Sie x
zuerst neu definieren müssen , damit dies funktioniert).
(def y (rest x))
(type y)
(first y)
- Die erste Lazy-Seq wird ausgewertet, was zum Ausdruck führt
first lazy-seq evaluated
- Dies führt zu einer nicht leeren Struktur: einem Nachteil mit 1 links und einem Lazy-Seq rechts.
rest
kehrt niemals zurück nil
, selbst wenn die Lazy-Seq auf der rechten Seite die leere Seq ergibt.
- Wenn der Anrufer mehr wissen muss (ist die Sequenz leer?), Kann er später den entsprechenden Test für die Lazy-Sequenz durchführen.
- Also sind wir fertig, geben Sie einfach die Lazy-Seq als Ergebnis zurück.
- Beim Erhalten des
first
von y
muss das Lazy-Seq einen Schritt weiter ausgewertet werden, um das 2 zu erhalten
Seitenleiste
Beachten Sie, dass y
der Typ ist LazySeq
. Dies mag offensichtlich erscheinen, ist aber LazySeq
nicht "Sache der Sprache", sondern "Sache der Laufzeit", die kein Ergebnis, sondern einen Berechnungszustand darstellt. In der Tat (type y)
ist clojure.lang.LazySeq
bedeutet nur , „wir wissen nicht , den Typ noch, Sie mehr zu erfahren zu tun haben“. Immer wenn eine Clojure-Funktion wie nil?
etwas trifft, das einen Typ hat clojure.lang.LazySeq
, wird eine Berechnung durchgeführt!
PS
In Joy of Clojure, 2. Auflage, auf Seite 126, wird anhand eines Beispiels iterate
der Unterschied zwischen next
und veranschaulicht rest
.
(doc iterate)
Wie sich herausstellt, funktioniert das Beispiel nicht. In diesem Fall gibt es tatsächlich keinen Unterschied im Verhalten zwischen next
und rest
. Ich next
weiß nicht warum, weiß vielleicht, dass es niemals nil
hierher zurückkehren wird und verwendet standardmäßig das Verhalten von rest
.
(defn print-then-inc [x] (do (print "[" x "]") (inc x)))
(def very-lazy (iterate print-then-inc 1))
(def very-lazy (rest(rest(rest(iterate print-then-inc 1)))))
(first very-lazy)
(def less-lazy (next(next(next(iterate print-then-inc 1)))))
(first less-lazy)
()
wahr undnil
falsch ist. Also(defn keep-going [my-coll] (if (rest my-coll) (keep-going (rest my-coll)) "Finished.")
wird es für immer weitergehen - oder besser gesagt, der Stapel wird überlaufen - während(defn keep-going [my-coll] (if (next my-coll) (keep-going (next my-coll)) "Finished.")
es fertig ist. (OP hat dies sicherlich inzwischen herausgefunden; ich füge diese Bemerkung für andere hinzu.)