Es fällt mir schwer, den Unterschied zwischen rest
und next
in Clojure zu verstehen . Die Seite der offiziellen Website über Faulheit zeigt an, dass die Präferenz wahrscheinlich die Verwendung sein sollte rest
, aber sie erklärt den Unterschied zwischen den beiden nicht wirklich klar. Kann jemand einen Einblick geben?
clojure
lazy-evaluation
Daniel Yankowsky
quelle
quelle
()
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.)Antworten:
Wie die von Ihnen verlinkte Seite beschrieben hat,
next
ist sie strenger als (das neue Verhalten von),rest
da sie die Struktur der faulen Nachteile bewerten muss, um zu wissen, ob sie zurückkehren sollnil
oder eine Folge.rest
Auf der anderen Seite wird immer eine Sequenz zurückgegeben, sodass nichts ausgewertet werden muss, bis Sie das Ergebnis von tatsächlich verwendenrest
. Mit anderen Worten,rest
ist fauler alsnext
.quelle
next
immer eine Nachteile-Zelle zurückgegeben wird, währendrest
lediglich ein ISeq (oder welcher Typ auch immer) zurückgegeben wird?next
zurücknil
oder ist nicht leerseq
.rest
Gibt immer ein zurückseq
, das möglicherweise leer ist.(drop 1 s)
ist fauler alsrest
weil es keine teilweise Verwirklichung von s erzwingt .rest
ist nicht garantiert, nur den 1. Punkt der Lazy Seq zu realisieren. Es kommt auf die faule Sequenz an.Es ist einfach, wenn Sie dies haben:
(next '(1)) => nil
So
next
schaut auf die nächste Sache , und wenn die Zeile leer ist es kehrtnil
anstelle eines leeren fg. Dies bedeutet, dass es nach vorne schauen muss (bis zum ersten Element, das es zurückgeben würde), was es nicht vollständig faul macht (möglicherweise benötigen Sie nicht den nächsten Wert,next
verschwenden aber die Rechenzeit, um nach vorne zu schauen).(rest '(1)) => ()
rest
schaut nicht nach vorne und gibt nur den Rest der Sequenz zurück.Vielleicht denkst du, warum überhaupt zwei verschiedene Dinge hier verwenden? Der Grund dafür ist, dass Sie normalerweise wissen möchten, ob in der Sequenz nichts mehr vorhanden ist, und einfach zurückkehren möchten.
nil
In einigen Fällen, in denen die Leistung sehr wichtig ist und die Bewertung eines weiteren Elements einen enormen Aufwand bedeuten kann, können Sie diesen verwendenrest
.quelle
next
ist wie(seq (rest ...))
.rest
gibt das verbleibende Stück einer Sequenz zurück. Wenn dieses Stück der Sequenz noch nicht realisiert wurde, wirdrest
es nicht erzwungen. Es wird Ihnen nicht einmal sagen, ob noch weitere Elemente in der Sequenz vorhanden sind.next
macht dasselbe, erzwingt dann aber, dass mindestens ein Element der Sequenz realisiert wird. Wenn Sie alsonext
zurückkehrennil
, wissen Sie, dass die Sequenz keine weiteren Elemente mehr enthält.quelle
Ich bevorzuge jetzt die Verwendung
next
mit Rekursion, da die Escape-Bewertung einfacher / sauberer ist:(loop [lst a-list] (when lst (recur (next lst))
vs.
(loop [lst a-list] (when-not (empty? lst) ;; or (when (seq? lst) (recur (rest lst))
Ein Fall für die Verwendung
rest
wäre jedoch, wenn Sie eine Sammlung als Warteschlange oder Stapel verwenden. In diesem Fall möchten Sie, dass Ihre Funktion eine leere Sammlung zurückgibt, wenn Sie das letzte Element entfernen oder aus der Warteschlange entfernen.quelle
lst
leer ist, weil die leere Liste wahr ist. Ein guter Weg, um auf "Nicht-Null" zu testen:(when (some? lst) ...
(über Tonsky.me/blog/readable-clojure )if-let
, wie in:(loop [lst lst] (if-let [[elem & lst] (seq lst)] (recur lst)))
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
undnext
. In Kombinationseq
damit ergibt sich die folgende Redewendung, bei der das Ende der Liste überseq
und der Rest der Liste überrest
(angepasst aus "The Joy of Clojure") getestet wird :; "when (seq s)": ; case s nonempty -> truthy -> go ; case s empty -> nil -> falsy -> skip ; case s nil -> nil -> falsy -> skip (defn print-seq [s] (when (seq s) (assert (and (not (nil? s)) (empty? s))) (prn (first s)) ; would give nil on empty seq (recur (rest s)))) ; would give an empty sequence on empty seq
Warum ist
next
eifriger alsrest
?Wenn
(next coll)
ausgewertet wird, kann das Ergebnis seinnil
. Dies muss sofort bekannt sein (dhnil
muss tatsächlich zurückgegeben werden), da der Anrufer basierend auf der Wahrheit von verzweigen kannnil
.Wenn
(rest coll)
ausgewertet wird, kann das Ergebnis nicht seinnil
. 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"))))))) ;=> #'user/x
Die Berechnung von "x" wird nun beim ersten "Lazy-Seq" ausgesetzt.
Mit dem eifrigen nächsten sehen wir zwei Bewertungen:
(def y (next x)) ;=> first lazy-seq evaluated ;=> second lazy-seq evaluated ;=> #'user/y (type y) ;=> clojure.lang.Cons (first y) ;=> 2
first lazy-seq evaluated
next
muss möglicherweise zurückkehren,nil
wenn der rechte Zweig leer ist. Wir müssen also eine Ebene tiefer prüfen.second lazy-seq evaluated
nil
, sondern die Nachteile zurückgeben.first
von erhalteny
, 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 Siex
zuerst neu definieren müssen , damit dies funktioniert).(def y (rest x)) ;=> first lazy-seq evaluated ;=> #'user/y (type y) ;=> clojure.lang.LazySeq (first y) ;=> second lazy-seq evaluated ;=> 2
first lazy-seq evaluated
rest
kehrt niemals zurücknil
, selbst wenn die Lazy-Seq auf der rechten Seite die leere Seq ergibt.first
vony
muss das Lazy-Seq einen Schritt weiter ausgewertet werden, um das 2 zu erhaltenSeitenleiste
Beachten Sie, dass
y
der Typ istLazySeq
. Dies mag offensichtlich erscheinen, ist aberLazySeq
nicht "Sache der Sprache", sondern "Sache der Laufzeit", die kein Ergebnis, sondern einen Berechnungszustand darstellt. In der Tat(type y)
istclojure.lang.LazySeq
bedeutet nur , „wir wissen nicht , den Typ noch, Sie mehr zu erfahren zu tun haben“. Immer wenn eine Clojure-Funktion wienil?
etwas trifft, das einen Typ hatclojure.lang.LazySeq
, wird eine Berechnung durchgeführt!PS
In Joy of Clojure, 2. Auflage, auf Seite 126, wird anhand eines Beispiels
iterate
der Unterschied zwischennext
und veranschaulichtrest
.(doc iterate) ;=> Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free ; of side-effects
Wie sich herausstellt, funktioniert das Beispiel nicht. In diesem Fall gibt es tatsächlich keinen Unterschied im Verhalten zwischen
next
undrest
. Ichnext
weiß nicht warum, weiß vielleicht, dass es niemalsnil
hierher zurückkehren wird und verwendet standardmäßig das Verhalten vonrest
.(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))))) ;=> [ 1 ][ 2 ]#'user/very-lazy (first very-lazy) ;=> [ 3 ]4 (def less-lazy (next(next(next(iterate print-then-inc 1))))) ;=> [ 1 ][ 2 ]#'user/less-lazy (first less-lazy) ;=> [ 3 ]4
quelle