为什么reduce在Clojure中给出了StackOverflowError?

时间:2014-07-25 15:10:38

标签: recursion clojure stack-overflow reduce

我正在尝试连接一系列Seqs。

我可以使用apply concat

user=> (count (apply concat (repeat 3000 (repeat 3000 true))))
9000000

然而,根据我有限的知识,我会假设使用apply强制实现懒惰的Seq,这对于非常大的输入似乎不正确。如果可以的话,我宁愿懒洋洋地这样做。

所以我认为使用reduce可以完成这项工作。

user=> (count (reduce concat (repeat 3000 (repeat 3000 true))))

但这会导致

StackOverflowError   clojure.lang.RT.seq (RT.java:484)

我很惊讶,因为我会认为reduce的语义意味着它是尾调用的递归。

两个问题:

  • apply是最好的方法吗?
  • reduce通常不适合大输入吗?

2 个答案:

答案 0 :(得分:27)

使用apply。当函数参数是惰性时,apply

也是如此

让我们检查对基础子序列的计数副作用:

(def counter (atom 0))

(def ss (repeatedly 3000 
          (fn [] (repeatedly 3000 
            (fn [] (do (swap! counter inc) true))))))


(def foo (apply concat ss))

so.core=> @counter
0

so.core=> (dorun (take 1 foo))
nil

so.core=> @counter
1

so.core=> (dorun (take 3001 foo))
nil

so.core=> @counter
3001

由于thunk组合而导致大量reduce s溢出的

concat

延迟序列,例如concat生成的序列,是通过thunk,延迟函数调用实现的。当你concat concat的结果时,你已经将thunk嵌套在另一个thunk中。在你的函数中,嵌套深度为3000,因此一旦请求第一个项目并且3000个嵌套的thunks被解开,堆栈就会溢出。

so.core=>  (def bar (reduce concat (repeat 3000 (repeat 3000 true))))
#'so.core/bar

so.core=> (first bar)
StackOverflowError   clojure.lang.LazySeq.seq (LazySeq.java:49)

seq编辑时,implementation of lazy-sequences通常会展开嵌套的thunks trampoline样式,而不是吹掉堆栈:

so.core=> (loop [lz [1], n 0] 
            (if (< n 3000) (recur (lazy-seq lz) (inc n)) lz))
(1)

但是,如果你在实现它时在未实现部分的懒惰序列中调用seq ...

so.core=> (loop [lz [1], n 0] 
            (if (< n 3000) (recur (lazy-seq (seq lz)) (inc n)) lz))
StackOverflowError   so.core/eval1405/fn--1406 (form-init584039696026177116.clj:1)

so.core=> (pst 3000)
StackOverflowError
        so.core/eval1619/fn--1620 (form-init584039696026177116.clj:2)
        clojure.lang.LazySeq.sval (LazySeq.java:40)
        clojure.lang.LazySeq.seq (LazySeq.java:49)
        clojure.lang.RT.seq (RT.java:484)
        clojure.core/seq (core.clj:133)
        so.core/eval1619/fn--1620 (form-init584039696026177116.clj:2)
        clojure.lang.LazySeq.sval (LazySeq.java:40)
        clojure.lang.LazySeq.seq (LazySeq.java:49)
        clojure.lang.RT.seq (RT.java:484)
        clojure.core/seq (core.clj:133)
        ... (repeatedly)

然后你最终构建seq堆栈帧。 concat的实施就是这样。使用concat检查StackOverflowError的堆栈跟踪,您将看到类似的内容。

答案 1 :(得分:1)

我可以建议一种避免问题的方法。 reduce功能不是问题所在; concat是。

看看: https://stuartsierra.com/2015/04/26/clojure-donts-concat

而不是使用concat使用into

(count (reduce into (repeat 3000 (repeat 3000 true))))
9000000