Clojureのrest
とnext
の違いを理解するのに苦労しています。 怠惰に関する公式サイトのページ は、おそらくrest
を使用することをお勧めしますが、2つの違いを明確に説明していません。誰かが洞察を提供できますか?
リンクしたページで説明されているように、next
は、怠惰な短所の構造を評価してrest
を返すかどうかを知る必要があるため、nil
(の新しい動作)よりも厳密です。またはseq。
一方、rest
は常にseqを返すため、実際にrest
の結果を使用するまで、何も評価する必要はありません。言い換えると、rest
はnext
よりも怠惰です。
これがあれば簡単です:
(next '(1))
=> nil
したがって、next
は次のものを調べ、行が空の場合、空のseqの代わりにnil
を返します。これは、(最初に返される項目に対して)先読みする必要があることを意味し、完全に遅延することはありません(多分、次の値は必要ありませんが、next
は先読みするために計算時間を浪費します)。
(rest '(1))
=> ()
rest
は先を見越さず、残りのシーケンスを返すだけです。
たぶん、どうしてここで2つの異なるものを使うのが面倒なのでしょうか。その理由は、通常、seqに何も残っていないかどうかを知り、nil
を返すだけですが、パフォーマンスが非常に重要で、もう1つの項目を評価することは、rest
を使用できる多大な労力を意味する場合があります。
next
は(seq (rest ...))
に似ています。
rest
は、シーケンスの残りの部分を返します。そのシーケンスの一部がまだ実現されていない場合、rest
はそれを強制しません。シーケンスに要素が残っているかどうかもわかりません。
next
も同じことを行いますが、シーケンスの少なくとも1つの要素を強制的に実現します。したがって、next
がnil
を返す場合、シーケンスに要素が残っていないことがわかります。
エスケープ評価がより単純でクリーンであるため、私は今、next
をreursionで使用することを好みます。
(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))
ただし、rest
を使用するケースは、コレクションをキューまたはスタックとして使用する場合です。その場合、最後のアイテムをポップまたはデキューするときに関数が空のコレクションを返すようにします。
「mundane」再帰を使用して(スタックを使用して)、またはrecur
を使用して(末尾再帰最適化を使用して実際にループする)、シーケンスを横断するコードを記述するときに役立つ小さなテーブルを次に示します。
rest
とnext
の動作の違いに注意してください。 seq
と組み合わせると、次のイディオムになります。ここで、リストの終わりはseq
を介してテストされ、リストの残りはrest
を介して取得されます(「The Clojureの喜び」):
; "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
next
がrest
より熱心なのはなぜですか?
(next coll)
が評価された場合、結果はnil
になります。呼び出し元はnil
の真実性に基づいて分岐する可能性があるため、これはすぐに知る必要があります(つまり、nil
を実際に返す必要があります)。
(rest coll)
が評価される場合、結果はnil
にはなりません。次に、呼び出し元が関数呼び出しを使用して結果の空性をテストしない限り、lazy-seqでの「次の要素」の生成は、実際に必要な時間まで遅らせることができます。
例
完全に怠惰なコレクションであり、すべての計算は「必要になるまで保留」されます
(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
"x"の計算は、最初の "lazy-seq"で中断されます。
その後、熱心に使用すると、2つの評価が表示されます。
(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
は、右側のブランチが空の場合、nil
を返す必要がある場合があります。したがって、1レベル深くチェックする必要があります。second lazy-seq evaluated
になります。nil
を返さず、代わりに短所を返します。first
のy
を取得する場合、すでに取得されている短所から2を取得する以外に何もしません。怠惰なrest
を使用すると、1つの評価が表示されます(これを機能させるには、最初にx
を再定義する必要があることに注意してください)。
(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
はnil
を返すことはありません。first
のy
を取得する場合、2を取得するには、lazy-seqをさらに1ステップ評価する必要があります。サイドバー
y
のタイプはLazySeq
であることに注意してください。これは当たり前のように思えるかもしれませんが、LazySeq
は「言語のもの」ではなく、「ランタイムのもの」であり、結果ではなく計算の状態を表しています。実際、(type y)
がclojure.lang.LazySeq
であるということは、「タイプがまだわからないので、調べるにはさらに多くのことをしなければならない」という意味です。 nil?
のようなClojure関数がタイプclojure.lang.LazySeq
の何かにヒットするたびに、計算が行われます。
P.S。
Joy of Clojure、第2版、126ページに、iterate
とnext
の違いを説明するためにrest
を使用する例があります。
(doc iterate)
;=> Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free
; of side-effects
結局のところ、この例は機能しません。この場合、実際にはnext
とrest
の動作に違いはありません。理由は不明ですが、おそらくnext
はここではnil
を返さず、デフォルトで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)))))
;=> [ 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