懒惰和堆栈溢出

时间:2012-05-28 15:05:36

标签: clojure stack-overflow lazy-evaluation lazy-sequences

我写了以下内容:

(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

要求重新实现 map 而不使用 map 等,并且该解决方案通过测试(我不知道它是否正确:它非常接近其他解决方案说)。

因为问题表明它必须是懒惰的我通过在 lazy-seq 中“包装”我的解决方案来编写上面的代码...但是我不明白 lazy是怎么回事-seq 有效。

我不明白这里什么是“懒惰”,也不知道如何测试它。

当我问(type ...)时,毫不奇怪,我得到了一个 clojure.lang.LazySeq ,但我不知道如果我只是删除<它与我之间的区别是什么em> lazy-seq “wrap”。

当然,如果我删除 lazy-seq ,我会得到一个stackoverflow为什么要尝试执行此操作:

(= [(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:现在运行良好的版本也会引发堆栈溢出。

现在我不确定:也许这是宏的一个不受欢迎的影响,会以某种方式强制评估那些否则不会被评估的东西?

如果有人能够解释,使用这个简单的函数和简单的测试,懒惰在这里是如何工作的,在什么时候调用什么等等,这将是很好的。

1 个答案:

答案 0 :(得分:4)

整个魔法在于clojure.lang.LazySeq java类。它本身实现了ISeq接口,并且lazy-seq宏的s-expressions参数被转换为没有任何参数的函数,并被传递给clojure.lang.LazySeq的构造函数(到构造函数中{{1} } object as parameter)因为最后你又调用了IFn函数(返回r而不是完整列表),这允许LazySeq懒惰地评估项目。

所以基本上流程是这样的:

  • LazySeq调用传递给它的Fn(即代码的其余部分)
  • 此Fn调用返回ISeq,因为Lists实现了ISeq。这返回ISeq(列表),第一个值作为具体值,第二个是LazySeq对象,因为递归调用ISeq。返回的ISeq存储在类中的局部变量中。
  • 调用下一个项目的LazySeq的ISeq实现会调用它在上一步中存储在本地类变量中的下一个ISeq(列表),并检查它是否为LazySeq类型(由于{它将在第二个项目中) {1}}调用),如果它是LazySeq然后评估并返回项目,否则直接返回项目(你传递给cons的第一个具体值)

我知道这是一个有点心灵弯曲的东西:)。我刚才也经历了Java代码,并且在我意识到魔术是可能的之后能够弄清楚因为对r本身的递归调用返回了一个懒惰的序列。所以你有它,一种自定义分隔的延续:)