我正在尝试连接一系列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
通常不适合大输入吗?答案 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
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