Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/clojure/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么Clojure主生成器会引发StackOverflower错误?_Clojure_Stack Overflow_Primes_Tail Recursion - Fatal编程技术网

为什么Clojure主生成器会引发StackOverflower错误?

为什么Clojure主生成器会引发StackOverflower错误?,clojure,stack-overflow,primes,tail-recursion,Clojure,Stack Overflow,Primes,Tail Recursion,我正在学习Clojure,和往常一样,在学习新的编程语言时,我首先尝试的事情之一就是实现Eratosthenes的筛选 我提出了以下解决方案: (defn primes "Calculate all primes up to the given number" [n] (loop [ result [] numbers (range 2 (inc n)) ] (if (empty? numbers) result (let [[nex

我正在学习Clojure,和往常一样,在学习新的编程语言时,我首先尝试的事情之一就是实现Eratosthenes的筛选

我提出了以下解决方案:

(defn primes
 "Calculate all primes up to the given number"
 [n]
 (loop
   [
    result []
    numbers (range 2 (inc n))
    ]
   (if (empty? numbers)
     result
     (let [[next & rest] numbers]
       (recur (conj result next) (filter (fn [n] (not= 0 (mod n next))) rest)))
     )
   )
 )
对于较小的数字,它工作良好且速度相当快,但对于较大的输入,StackOverflowerError会以可疑的短stacktrace引发,例如:

(primes 100000)
Execution error (StackOverflowError) at (REPL:1).
null
(pst)
StackOverflowError 
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:51)    
    clojure.lang.RT.seq (RT.java:531)
    clojure.core/seq--5387 (core.clj:137)    
    clojure.core/filter/fn--5878 (core.clj:2809)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:51)
    clojure.lang.RT.seq (RT.java:531)
    clojure.core/seq--5387 (core.clj:137)
    clojure.core/filter/fn--5878 (core.clj:2809)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:51)
=> nil
我的印象是,如果递归在循环形式中最后一次求值,它就会实现尾部递归。我的第一个问题是,这里是否真的是这样。我的第二个问题是为什么堆栈跟踪对于StackOverflowerr来说如此短。我在解释stacktrace时也有问题,即哪一行对应于哪一种形式


我只对更好或更像Clojure的解决方案感兴趣,如果它们能为这些问题提供见解,因为否则我想自己找到它们。谢谢大家!

以下是一个有效的版本:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(defn primes
  "Calculate all primes up to the given number"
  [n]
  (loop [result  []
         numbers (range 2 (inc n))]
    (if (empty? numbers)
      result
      (let [[new-prime & candidate-primes] numbers]
        (recur
          (conj result new-prime)
          (filterv (fn [n] (not= 0 (mod n new-prime)))
            candidate-primes))) )))

(dotest
  (spyx (primes 99999))
  )
结果:

-------------------------------
   Clojure 1.10.1    Java 13
-------------------------------

Testing tst.demo.core
(primes 99999) => [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 
67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 
167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 
269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 
379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 
487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 
...<snip>...
99401 99409 99431 99439 99469 99487 99497 99523 99527 99529 99551 99559 
99563 99571 99577 99581 99607 99611 99623 99643 99661 99667 99679 99689 
99707 99709 99713 99719 99721 99733 99761 99767 99787 99793 99809 99817 
99823 99829 99833 99839 99859 99871 99877 99881 99901 99907 99923 99929 
99961 99971 99989 99991]
惰性序列作为嵌套函数调用实现。由于查找prime 99991的最后一个循环依赖于查找prime 2的第一个调用,因此无法释放早期的惰性序列(以及它们在堆栈上的嵌套函数调用),最终导致堆栈崩溃

在我的计算机上,阶乘(N)的一个简单递归实现在N=4400左右爆炸。上面发现了9592个素数,因此它的具体原因似乎比每个素数1个堆栈帧要复杂一些

N=32分块可能会起到一定作用



为了避免由于不必要的惰性而导致的错误,您可能有兴趣将
concat
替换为,并将
for
替换为。您还可以看到。

稍有修改,并带有注释来描述每行上发生的情况,这是您的功能:

(defn素数)
“计算到给定数字的所有素数”
[n]
;‘loop’不是惰性的,它会一直运行直到产生一个结果:
(循环[结果[])
;clojure.lang.LongRange实现的惰性序列:
数字(范围2(包括n))]
(如果(不是(无)(序号)))
结果
(让[当前(第一个数字)
剩余(剩余人数)]
(重现
;;;向量上的'conj'返回一个向量(非惰性):
(conj结果当前)
;;;;惰性序列上的“filter”返回一个新的惰性序列:
(过滤器(fn[n](非=0(模块n下一个)))
剩余(()()()))
关键是最后的
过滤器

大多数惰性序列操作(如
filter
)都是通过将一个惰性序列包装到另一个惰性序列来工作的。在循环的每次迭代中,
filter
添加另一层惰性序列,如下所示:

(过滤器(fn[n](not=0(mod n 5));返回一个LazySeq
(filter(fn[n](not=0(mod n4));返回一个LazySeq
(filter(fn[n](not=0(mod n3));返回一个LazySeq
(filter(fn[n](not=0(mod n2));返回一个LazySeq
余下的))
LazySeq
对象堆叠起来,每个对象都包含对前一个对象的引用

对于大多数惰性序列,包装并不重要,因为只要您请求一个值,包装就会自动“展开”。这种情况发生在中国

在这种情况下,确实很重要,因为循环构建了很多层的惰性序列对象,对
LazySeq.seq
.sval
的嵌套调用溢出了JVM允许的最大堆栈大小。这就是你在stacktrace中看到的

(这也涉及到内存问题,因为引用序列的开头可以防止其他任何一个被垃圾收集,Clojure程序员称之为“抓住序列的头部”)

此函数更普遍的问题是混合了惰性(
过滤器
)和非惰性(
循环
)操作。这通常是问题的根源,因此Clojure程序员出于习惯学会了避免它

正如Alan所建议的,您可以通过只使用非惰性操作来避免问题,例如
filterv
,而不是
filter
,后者将惰性序列强制为向量

几乎任何类型的惰性评估都可能表现出这个问题的一些变化。我在书中描述了它。另一个例子见Haskell


即使没有惰性,深度嵌套的对象树也会导致堆栈溢出,例如在我找到的Java或中。

问题是filter是惰性的,这意味着它在需要第一个元素之前不会调用filter函数。它堆叠过滤器函数调用,这意味着您将得到100000个递归调用,从而破坏了堆栈。因此,您需要的是渴望过滤器,即将过滤器更改为filterv。我应该做这个把戏非常感谢你的详细解释,斯图尔特。我已经读了你的Clojure dont的帖子,因为Alan在他的回答中链接了它。我想我现在很了解这个问题。作为未来的Clojure开发专家,我至少会避免这个特殊问题;-)非常感谢你的详细解释,艾伦。再加上斯图亚特的回答和他的Clojure dont的帖子,我想我现在已经很好地理解了这个问题。
(filter ...
  (filter ...
    (filter ...
      (filter ...
         ...<many, many more>... ))))