6

私は次のように書いた:

(fn r [f xs]
  (lazy-seq
    (if (empty? xs)
    '()
    (cons (f (first xs)) (r f (rest xs))))))

4clojure.com の問題 #118 を解決するには: http://www.4clojure.com/problem/118

マップなどを使用せずにマップを再実装するように要求し、そのソリューションはテストに合格します(それが正しいかどうかはわかりません。他のソリューションに非常に近いです)。

問題は怠惰でなければならないと述べていたので、私の解決策をlazy-seqで「ラップ」して上記のコードを書きました...しかし、lazy- seqがどのように機能するかわかりません。

ここで「怠け者」とは何か、またそれをテストする方法がわかりません。

私が尋ねる(type ...)と、当然のことながら、clojure.lang.LazySeqが得られますが、それと単にレイジー seqの「ラッピング」を削除した場合に得られるものとの違いは何なのかわかりません。

もちろん、lazy-seq を削除する、これを実行しようとする理由でスタック オーバーフローが発生します。

(= [(int 1e6) (int (inc 1e6))]
   (->> (... inc (range))
        (drop (dec 1e6))
        (take 2)))

それ以外の場合 (つまり、lazy-seq ラッピングを適切に行えば)、問題なく動作するようです。

そこで、どういうわけか「デバッグ」/何が起こっているのかを追跡して、すべてがどのように機能するかを理解しようとすることにしました。次のマクロを使用しました (SO IIRC で見つけました)。

(defmacro dbg [x] `(let [x# ~x] (println "dbg: " '~x "=" x#) x#))

そして、作業バージョンをdbgマクロ内にラップし、再度実行しようとしました。そして今、kaboom: 正常に動作していたバージョンでも、stackoverflow がスローされるようになりました。

今はよくわかりません: マクロの望ましくない効果で、他の方法では評価されないものの評価を何らかの形で強制するのでしょうか?

この単純な関数と単純なテストを使用して、ここで遅延がどのように機能するか、正確には何が呼び出されるかなどを誰かが説明できれば素晴らしいと思います.

4

1 に答える 1

4

全体の魔法はclojure.lang.LazySeq Java クラスにあります。それ自体が ISeq インターフェースを実装し、lazy-seqマクロへの s-expressions パラメーターは、パラメーターなしの関数に変換され、clojure.lang.LazySeq のコンストラクター (IFnオブジェクトをパラメーターとして受け取るコンストラクター) に渡されます。r関数を再度呼び出しました (ISeq完全なリストではなく a を返します)。これにより、LazySeq はアイテムを遅延して評価できます。

つまり、基本的には次のような流れになります。

  • LazySeq は渡された Fn を呼び出します (つまり、コードの残りの部分)
  • Lists は ISeq を実装しているため、この Fn 呼び出しは ISeq を返します。これは、最初の値が具体的な値であり、2 番目の値が への再帰呼び出しによる LazySeq オブジェクトである ISeq (リスト) を返しますr。この返された ISeq は、クラスのローカル変数に格納されます。
  • 次のアイテムを呼び出す際の LazySeq の ISeq 実装は、上記の手順でローカル クラス変数に格納された ISeq (リスト) の次を呼び出し、それがタイプ LazySeq (r呼び出しにより 2 番目のアイテムになります) であるかどうかを確認します。それはLazySeqであり、それを評価し、次にアイテムを返します。それ以外の場合は、アイテムを直接返します(consに渡した最初の具体的な値)

私はそれが少し心を曲げるものであることを知っています:)。rまた、先ほど Java コードを調べたところ、それ自体への再帰呼び出しが遅延シーケンスを返すため、魔法が可能であることがわかりました。それで、あなたはそれを持っています、一種のカスタム区切りの継続:)

于 2012-05-28T16:40:43.367 に答える