我写了以下内容:
(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 并且该解决方案通过了测试(我不知道它是否正确:它非常接近所说的其他解决方案)。
因为问题表明它必须是惰性的,所以我通过将我的解决方案“包装”在惰性序列中来编写上面的代码......但是我不明白惰性序列是如何工作的。
我不明白这里的“懒惰”是什么,也不明白如何测试它。
当我问
(type ...)
不出所料,我得到了一个 clojure.lang.LazySeq 但我不知道这与我简单地删除惰性序列“包装”有什么区别。现在当然如果我 删除 延迟序列我得到一个stackoverflow为什么要执行这个:
(= [(int 1e6) (int (inc 1e6))]
(->> (... inc (range))
(drop (dec 1e6))
(take 2)))
否则(即:如果我让惰性序列包装到位),它似乎工作正常。
所以我决定尝试以某种方式“调试”/跟踪正在发生的事情,以试图了解它是如何工作的。我采用了以下宏(我在 SO IIRC 上找到的):
(defmacro dbg [x] `(let [x# ~x] (println "dbg: " '~x "=" x#) x#))
并将工作版本包装在 dbg 宏中并尝试再次执行它。现在kaboom:运行良好的版本现在也抛出了stackoverflow。
现在我不确定:也许这是宏的不良影响,会以某种方式强制评估否则不会被评估的东西?
如果有人能解释一下,使用这个简单的函数和简单的测试,懒惰在这里是如何工作的,什么时候被调用等等,那就太好了。
最佳答案
整个魔力在于clojure.lang.LazySeq java类。它本身实现了 lazy-seq
的 ISeq 接口(interface)和 s-expressions 参数宏被转换为不带任何参数的函数并传递给 clojure.lang.LazySeq 的构造函数(传递给以 IFn
对象为参数的构造函数),因为最后你调用了 r
再次函数(返回 ISeq
而不是完整列表)这允许 LazySeq 懒惰地评估项目。
所以基本上流程是这样的:
r
,此返回 ISeq (list),第一个值作为具体值,第二个是 LazySeq 对象.返回的 ISeq 存储在类的局部变量中。 r
调用,它将位于第二项中),如果是 LazySeq 则评估并返回然后 item else 直接返回该项目(您传递给 cons 的第一个具体值)我知道这有点让人费解:)。我刚才也浏览了 Java 代码,当我意识到这个魔法是可能的,因为对
r
的递归调用后才弄清楚。本身返回一个惰性序列。所以你有它,一种自定义分隔的延续:)
关于clojure - 懒惰和堆栈溢出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10786765/