【问题标题】:How to use LazySeq correctly如何正确使用 LazySeq
【发布时间】:2013-04-30 07:48:23
【问题描述】:

当我使用lazySeq 时有很多困惑。

问题:

(def fib
  (lazy-seq
    (concat [0 1] (map + fib (rest fib))))) ;; It's Ok
(take 10 fib) ;; Bomb

收到错误消息:StackOverflowError clojure.lang.RT.more (RT.java:589)

以下解决方案效果很好:

(def fib
  (concat [0 1] (lazy-seq (map + fib (rest fib))))) ;; Works well

(def fib
  (lazy-cat [0 1] (map + fib (rest fib)))) ;; Works well

concatmap 都返回惰性序列,为什么上面的程序看起来很像但有区别?

更详细地说,为什么第一个示例(lazy-seq 包装 concat)失败,但下面的示例(lazy-seq 包装 map)成功?

【问题讨论】:

  • @DominicKexel,实际上不一样,我的问题是关注lazy-seq 的顺序(或位置)。在第一个示例中,lazy-seq 位于concat 之外并使用炸弹,但第二个示例lazy-seq 只是包裹map 并且效果很好。所以我认为lazy-seq 的顺序很重要,这就是我想知道的,你的链接没有解释它。
  • 我很确定这个问题的答案很好地解释了这一点。
  • mikera(谁的回答被接受了)说“你放什么顺序并不重要。”。但我的问题表明订单很重要。您认为答案如何解释得很好?我读了问题和答案,发现这不是我想要的,你有没有清楚地读过我的问题? @DominicKexel
  • @mikera 期待您的回答。

标签: clojure lazy-sequences


【解决方案1】:

问题是在地图操作中使用rest。基本上,当您的惰性序列将调用表达式:(concat [0 1] (map + fib (rest fib))) 以返回一个 ISeq 对象时,将发生对 fib 的rest 调用(因为这是map 的参数,它必须首先执行,然后传递给 map 和map 是惰性的,但在我们达到惰性之前调用了 rest)。 rest 将尝试在作为 LazySeq 对象的 fib 上调用 more,并且更多将导致 fib 惰性 seq 获得下一个 ISeq,这再次涉及执行整个 concat 操作,其中涉及 rest它一直这样下去,直到堆栈被吹走。

您需要在 fib 上使用不会立即调用 next 的东西,例如 drop:

(def fib
  (lazy-seq
   (concat [0 1] (map + fib (drop 1 fib)))))

另外,在 lazy-seq 位于 concat 内部的其他情况下,rest 不会被执行,因为它被包裹在 lazy-seq 操作中,这使得整个表达式成为将来在下一个 ISeq 时调用的函数请求。

希望这能解决问题。

【讨论】:

  • 哇!就是这样!我输入了(take 10 (rest (iterate inc 1))) 并且代码有效,所以我认为rest 或多或少是“懒惰的”,但rest 的文档并没有说它返回lazy sequence。我认为这是最令人困惑的事情。
  • ISeq(clojure 中惰性的基础)与您的 IEnumerable(在 CLR 中)或 Iterators(在 Java 中)不同。在这些事情中,next 方法返回下一个对象,因此它们必须保持有关当前位置的状态,而 ISeq next 方法返回一个 ISeq(不是对象),因此它不需要任何状态。就像迭代器是:[1 2 3 4],ISeq 是 [1 [2 [3 [4 nil]]]]。这种差异有时是造成混淆的原因
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-04
  • 2012-07-12
  • 2013-01-21
  • 2021-10-24
  • 2011-12-22
相关资源
最近更新 更多