Clojure 为什么我会遇到堆垛溢出?

Clojure 为什么我会遇到堆垛溢出?,clojure,Clojure,我一直在和Clojure玩,我成功地把事情搞砸了。在这段代码中,我只对recur使用递归。我正在进行大量的连接(注意下面跟踪中对concat的调用)。我尝试在所有连接的列表上执行所有操作,因为这些列表是惰性的,我希望在执行时对它们进行评估。我仍然有堆栈溢出。这是痕迹。我想这可能是一个常见的问题,有经验的黑客可以告诉我正确的方向 这是导致问题的一段代码 (defn move-split [[xs ys]] (doall (concat (list (concat xs (list (first y

我一直在和Clojure玩,我成功地把事情搞砸了。在这段代码中,我只对recur使用递归。我正在进行大量的连接(注意下面跟踪中对concat的调用)。我尝试在所有连接的列表上执行所有操作,因为这些列表是惰性的,我希望在执行时对它们进行评估。我仍然有堆栈溢出。这是痕迹。我想这可能是一个常见的问题,有经验的黑客可以告诉我正确的方向

这是导致问题的一段代码

(defn move-split [[xs ys]] (doall (concat (list (concat xs (list (first ys))))  (list (next ys)))))
(defn move-split [[xs ys]] (doall (concat (list (doall (concat xs (list (first ys))))  ) (doall (list (next ys)))   )))
由于堆栈溢出,我将doall放在那里,但这仍然没有解决问题

(defn move-split [[xs ys]] (doall (concat (list (concat xs (list (first ys))))  (list (next ys)))))
(defn move-split [[xs ys]] (doall (concat (list (doall (concat xs (list (first ys))))  ) (doall (list (next ys)))   )))
注意到额外的道尔了吗?在这里,无论我在哪里调用concat,我都会通过doall过滤结果。他走了

doall似乎不是递归的。也就是说,不计算也是concat结果的嵌套列表。你觉得怎么样

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at jline.ConsoleRunner.main(Unknown Source)
Caused by: java.lang.StackOverflowError (bubble_sort2.clj:0)
    at clojure.lang.Compiler.eval(Compiler.java:5440)
    at clojure.lang.Compiler.load(Compiler.java:5857)
    at clojure.lang.Compiler.loadFile(Compiler.java:5820)
    at clojure.main$load_script.invoke(main.clj:221)
    at clojure.main$script_opt.invoke(main.clj:273)
    at clojure.main$main.doInvoke(main.clj:354)
    at clojure.lang.RestFn.invoke(RestFn.java:409)
    at clojure.lang.Var.invoke(Var.java:365)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:482)
    at clojure.main.main(main.java:37)
    ... 5 more
Caused by: java.lang.StackOverflowError
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)
    at clojure.lang.LazySeq.sval(LazySeq.java:42)
    at clojure.lang.LazySeq.seq(LazySeq.java:56)
    at clojure.lang.RT.seq(RT.java:450)
    at clojure.core$seq.invoke(core.clj:122)
    at clojure.core$concat$fn__3450.invoke(core.clj:599)

你在一排堆积一堆懒惰的操作,建立一个表达式,比如

(concat (concat (concat ... [3]) [2]) [1])

或类似的。为了确定结果列表中的第一个元素,编译器必须深入研究您提供给它的函数堆栈。试着构造代码,这样就不会发生这种情况,或者每隔一段时间就抛出一个(doall)来强制进行急切/严格的计算。不过,我不能仅仅通过堆栈跟踪来深入了解更多细节-代码会有所帮助。

现在OP已经提供了一些源代码并提出了一个不同的问题,添加了另一个答案。如果这是错误的,有人适当地编辑我

看起来您正在将[[1 2 3 4][5 6 7]]转换为[[1 2 3 4 5][6 7]]。列表和懒散的concat并不是解决这个问题的正确方法,这也是它给你带来如此多痛苦的原因之一。我的直觉是从一个向量[1 2 3 4 5 6 7]开始,你永久地保留它,并根据需要构造该向量的子向量
subvec
构造和使用起来既便宜又快捷

例如:

(defn vector-split [v at]
  [(subvec v 0 at) (subvec v at)])

我不确定是什么输入导致了你的函数崩溃。我不能让它以两个50亿元素的子序列爆炸:

boot.user=> (defn move-split [[xs ys]]
       #_=>   (doall
       #_=>     (concat
       #_=>       (list (concat xs (list (first ys))))
       #_=>       (list (next ys)))))
#'boot.user/move-split
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
((0 1 2 3 4 0) (1 2 3 4))
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
然而,有很多机会可以让这个函数更地道。看看函数:

(defn move-split [[xs ys]]
  (doall
    (concat
      (list (concat xs (list (first ys))))
      (list (next ys)))))
第4行:我们不需要构建一个
列表,因为
concat
已经返回了一个序列。同样,我们也不需要在第5行构建
列表,
next
已经返回了一个序列。有了这两个变化:

(defn move-split [[xs ys]]
  (doall
    (concat
      (concat xs (list (first ys)))
      (next ys))))
现在,我们为什么要在顶部粘贴一个
doall
,从而搞乱一个完美的(懒惰的)函数呢?让我们把它处理掉。另外,由于函数需要返回两个元素的序列,为什么不直接返回,而不是在第3行调用
concat
?构造两元素序列的惯用方法是通过向量文字语法。有了这两个变化,我们:

(defn move-split [[xs ys]]
  [(concat xs (list (first ys)))(next ys)])
哦,我们可以再次应用向量文字语法,代替
列表
构造函数,给我们这个最终版本:

(defn move-split [[xs ys]]
  [(concat xs [(first ys)])(next ys)])
现在让我们试试这个fn

boot.user=> (defn move-split [[xs ys]]
       #_=>   [(concat xs [(first ys)])(rest ys)])
#'boot.user/move-split
boot.user=> [(range 5e0) (range 5e0)]
[(0 1 2 3 4) (0 1 2 3 4)]
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
[(0 1 2 3 4 0) (1 2 3 4)]
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
这似乎奏效了。如果我们有两个巨大的子序列呢?让我们用几个50亿元素的子序列来试试:

boot.user=> (defn move-split [[xs ys]]
       #_=>   (doall
       #_=>     (concat
       #_=>       (list (concat xs (list (first ys))))
       #_=>       (list (next ys)))))
#'boot.user/move-split
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
((0 1 2 3 4 0) (1 2 3 4))
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
对于您最初的问题,正如我所说的,我不知道是什么输入导致原始函数破坏堆栈。我确实在文档中看到了这一点:

连接的连接不会自动展平!因此,带有concat的clj::clojure.core/reduce将生成大量嵌套结构,并且在尝试遍历生成的序列时,很容易生成堆栈溢出。(应用混凝土…)应优先考虑

让我们使用
reduce
concat
一个由10个整数组成的10个序列:

boot.user=> (take 5 (reduce concat [] (for [x (range 1e1)] (range 1e1))))
(0 1 2 3 4)
但是如果我们尝试使用
减少为
concat
一个10个整数的10000个序列:

boot.user=> (take 5 (reduce concat [] (for [x (range 1e5)] (range 1e1))))

java.lang.StackOverflowError: 
使用
apply
可以正常工作:

boot.user=> (take 5 (apply concat (for [x (range 1e5)] (range 1e1))))
(0 1 2 3 4)

递归应用concat时需要记住的一点。

如果您也发布导致异常的代码,可能会有所帮助。发布时附带附加注释。一些代码已发布。嵌套的concat就是问题所在。在我发布这个之前,我怀疑。但我发现我必须将一个doall与每个concat配对。移动拆分函数显然不是我要走的路,我已经离开了它,但我不知道它为什么会导致问题,这让我很困扰。我想我现在明白了。谢谢你的建议。我的clojure工具箱还有别的东西。