web-dev-qa-db-ja.com

Clojure:休憩vs.次

Clojureのrestnextの違いを理解するのに苦労しています。 怠惰に関する公式サイトのページ は、おそらくrestを使用することをお勧めしますが、2つの違いを明確に説明していません。誰かが洞察を提供できますか?

67

リンクしたページで説明されているように、nextは、怠惰な短所の構造を評価してrestを返すかどうかを知る必要があるため、nil(の新しい動作)よりも厳密です。またはseq。

一方、restは常にseqを返すため、実際にrestの結果を使用するまで、何も評価する必要はありません。言い換えると、restnextよりも怠惰です。

62
sepp2k

これがあれば簡単です:

(next '(1))
=> nil

したがって、nextは次のものを調べ、行が空の場合、空のseqの代わりにnilを返します。これは、(最初​​に返される項目に対して)先読みする必要があることを意味し、完全に遅延することはありません(多分、次の値は必要ありませんが、nextは先読みするために計算時間を浪費します)。

(rest '(1))
=> ()

restは先を見越さず、残りのシーケンスを返すだけです。

たぶん、どうしてここで2つの異なるものを使うのが面倒なのでしょうか。その理由は、通常、seqに何も残っていないかどうかを知り、nilを返すだけですが、パフォーマンスが非常に重要で、もう1つの項目を評価することは、restを使用できる多大な労力を意味する場合があります。

31
nickik

next(seq (rest ...))に似ています。

restは、シーケンスの残りの部分を返します。そのシーケンスの一部がまだ実現されていない場合、restはそれを強制しません。シーケンスに要素が残っているかどうかもわかりません。

nextも同じことを行いますが、シーケンスの少なくとも1つの要素を強制的に実現します。したがって、nextnilを返す場合、シーケンスに要素が残っていないことがわかります。

21
Stuart Sierra

エスケープ評価がより単純でクリーンであるため、私は今、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を使用するケースは、コレクションをキューまたはスタックとして使用する場合です。その場合、最後のアイテムをポップまたはデキューするときに関数が空のコレクションを返すようにします。

2
Terje Dahl

「mundane」再帰を使用して(スタックを使用して)、またはrecurを使用して(末尾再帰最適化を使用して実際にループする)、シーケンスを横断するコードを記述するときに役立つ小さなテーブルを次に示します。

rest, next, first, seq, oh my!

restnextの動作の違いに注意してください。 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

nextrestより熱心なのはなぜですか?

(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
  • 最初のlazy-seqが評価され、印刷出力first lazy-seq evaluatedになります。
  • これにより、空でない構造になります。左側に1があり、右側にlazy-seqがある短所です。
  • nextは、右側のブランチが空の場合、nilを返す必要がある場合があります。したがって、1レベル深くチェックする必要があります。
  • 2番目のlazy-seqが評価され、印刷出力second lazy-seq evaluatedになります。
  • これは空でない構造になります:左側が2で右側がlazy-seqの短所。
  • したがって、nilを返さず、代わりに短所を返します。
  • firstyを取得する場合、すでに取得されている短所から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
  • これにより、空でない構造になります。左側に1があり、右側にlazy-seqがある短所です。
  • 右側のlazy-seqが空のseqと評価されたとしても、restnilを返すことはありません。
  • 呼び出し元が詳細を知る必要がある場合(seqは空ですか?)、後でlazy-seqで適切なテストを実行できます。
  • これで完了です。結果としてlazy-seqを返すだけです。
  • firstyを取得する場合、2を取得するには、lazy-seqをさらに1ステップ評価する必要があります。

サイドバー

yのタイプはLazySeqであることに注意してください。これは当たり前のように思えるかもしれませんが、LazySeqは「言語のもの」ではなく、「ランタイムのもの」であり、結果ではなく計算の状態を表しています。実際、(type y)clojure.lang.LazySeqであるということは、「タイプがまだわからないので、調べるにはさらに多くのことをしなければならない」という意味です。 nil?のようなClojure関数がタイプclojure.lang.LazySeqの何かにヒットするたびに、計算が行われます。

P.S。

Joy of Clojure、第2版、126ページに、iteratenextの違いを説明するためにrestを使用する例があります。

(doc iterate)
;=> Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free
;   of side-effects

結局のところ、この例は機能しません。この場合、実際にはnextrestの動作に違いはありません。理由は不明ですが、おそらく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
0
David Tonhofer