在正常情况下,reduce使用loop/recur构造操作并使用常量堆栈空间。但是,您遇到了一个令人讨厌的极端情况,这是由于减少了通过提供concat交替的分块和非分块序列(向量[0]是分块的;产生的序列是非(take 100 (repeat %))分块的)而产生的序列。
当第一个参数concat是分块序列时,它将返回一个惰性序列,该序列将用于chunk-cons生成另一个分块序列。否则,它将cons用于生成非分块序列。
同时, 的实现reduce使用InternalReduce协议(定义在 中clojure.core.protocols),该协议internal-reduce为结构提供了一个函数,可以比默认的第一次/下一次递归更有效地减少自身。分块序列的internal-reduce实现使用分块函数在循环中消耗分块项目,直到它留下一个非分块序列,然后调用internal-reduce其余部分。默认internal-reduce实现类似地使用first/next在循环中使用项目,直到底层 seq 类型发生更改,然后调用internal-reduce新的 seq 类型以分派到适当的优化版本。随着您在生成的序列中前进concat,在分块和非分块子序列之间交替,internal-reduce调用堆积在堆栈上并最终炸毁它。
这种情况的一个更简单的说明是:
;; All chunked sub-seqs is OK
user> (reduce + (apply concat (take 10000 (repeat [1]))))
10000
;; All non-chunked sub-seqs is OK
user> (reduce + (apply concat (take 10000 (repeat '(1)))))
10000
;; Interleaved chunked and non-chunked sub-seqs blows the stack
user> (reduce + (apply concat (take 10000 (interleave (repeat [1]) (repeat '(1))))))
StackOverflowError clojure.lang.LazySeq.seq (LazySeq.java:60)
检查堆栈跟踪:
StackOverflowError
clojure.core/seq (core.clj:133)
clojure.core/interleave/fn--4525 (core.clj:3901)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.RT.seq (RT.java:484)
clojure.core/seq (core.clj:133)
clojure.core/take/fn--4232 (core.clj:2554)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.Cons.next (Cons.java:39)
clojure.lang.RT.next (RT.java:598)
clojure.core/next (core.clj:64)
clojure.core/concat/cat--3925/fn--3926 (core.clj:694)
clojure.lang.LazySeq.sval (LazySeq.java:42)
clojure.lang.LazySeq.seq (LazySeq.java:60)
clojure.lang.ChunkedCons.chunkedNext (ChunkedCons.java:59)
clojure.core/chunk-next (core.clj:660)
clojure.core.protocols/fn--6041 (protocols.clj:101)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6034 (protocols.clj:147)
clojure.core.protocols/fn--6005/G--6000--6014 (protocols.clj:19)
clojure.core.protocols/fn--6041 (protocols.clj:104)
至于您的解决方法:
- 显然,避免
reduce完全避免了问题。
- 更改
[0]为'(0)用非分块子序列替换分块子序列,绕过对分块序列的优化,internal-reduce并允许减少发生在具有恒定堆栈空间的单个循环中。
- 插入
take创建一个新的、非分块的序列,完全由 cons 单元组成。