使用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
reduce 大量 concats 因 thunk 组合而溢出
惰性序列,例如concat 产生的序列,是通过 thunk、延迟函数调用实现的。当您 concat concat 的结果时,您已将一个 thunk 嵌套在另一个 thunk 中。在您的函数中,嵌套深度为 3000,因此一旦请求第一项,堆栈就会溢出,并且 3000 个嵌套的 thunk 被解除。
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)
implementation of lazy-sequences 通常会在seqed 时展开嵌套的 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)
堆栈溢出错误
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)
...(反复)
然后你最终构建了seq 堆栈帧。 concat 的实现就是这样。使用 concat 检查 StackOverflowError 的堆栈跟踪,您会看到类似的结果。