【问题标题】:Lazyness and stackoverflow懒惰和stackoverflow
【发布时间】:2012-06-02 22:41:49
【问题描述】:

我写了以下内容:

(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,但我不知道这与我简单地删除 有什么区别惰性序列“包装”。

当然,如果我 remove lazy-seq 我会得到一个 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 已经实现了?测试函数来测试惰性序列的值是否已经实现。例如(已实现?(范围))将是错误的。

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


【解决方案1】:

整个魔力在于clojure.lang.LazySeq java 类。它本身实现了 ISeq 接口和 lazy-seq 宏的 s-expressions 参数被转换为没有任何参数的函数并传递给 clojure.lang.LazySeq 的构造函数(传递给以 IFn 对象为参数的构造函数) 并且因为最后您再次调用了 r 函数(返回的是 ISeq 而不是完整列表)这允许 LazySeq 懒惰地评估项目。

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

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

我知道这有点让人费解:)。我刚才还浏览了 Java 代码,并且在我意识到魔术是可能的之后能够弄清楚,因为对 r 的递归调用本身返回了一个惰性序列。所以你有它,一种自定义分隔的延续:)

【讨论】:

  • 澄清这个习语中不涉及递归可能会有所帮助,因为r 的返回值是LazySeq,只有在实现头部时才会再次调用r。实现总是发生在r 已经返回之后。
猜你喜欢
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 2020-05-04
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多