在正常情况下,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 单元组成。