2024 Clojure调研!分享您的想法。

欢迎!请参阅关于页面了解有关如何使用本网站的一些更多信息。

+1
序列

在随机操作大斐波那契数时,我发现了def和let之间似乎的不一致性。


user=> (def fibs (fn ([] (fibs 1N 1N)) ([a b] (lazy-seq (cons a (fibs b (+ a b))))))) 'user/fibs user=> (take 20 (fibs))
(1N 1N 2N 3N 5N 8N 13N 21N 34N 55N 89N 144N 233N 377N 610N 987N 1597N 2584N 4181N 6765N)
user=> (def f1m (nth (fibs) 1000000))
OutOfMemoryError Java heap space java.math.BigInteger.add (BigInteger.java:1315)
user=> (def f1m (let [x (nth (fibs) 1000000)] x)) 'user/f1m user=> (take 20 (str f1m))
(\3 \1 \6 \0 \4 \7 \6 \8 \7 \3 \8 \6 \6 \8 \9 \8 \7 \3 \4 \4)
user=> (count (str f1m))
208988

为什么def会导致内存增加,而let则不会?def是否以一种let不成的方式保留懒惰列表的头部?

1 个答案

+2

这实际上是eval如何处理REPL中的“简单”表达式与“复杂”表达式的不同。

对于看起来足够简单的表达式(如下,其中值会在REPL中调用其他函数的def)eval实际上会像解释器一样工作,并不会生成JVM的字节码来运行。保持头部的是解释器代码路径,比生成的字节码要原始得多。

let表达式、函数、基本上除了字面值或带有简单参数的函数调用之外的任何东西,都是足够复杂的,需要编译成字节码,字节码对参数的处理更为谨慎。

"简单表达式"的行为是优化还是必要的?
很难说,通常被描述为一种优化。Clojure最初针对Java 5,在创建许多小类方面有显著的成本(如果你要生成字节码并执行它,它必须存在于一个类中)。如今,Java在这方面有所改进。所以可能只需始终生成字节码即可。

解释器的代码实际上看起来像是在尝试避免持有头,但不知何故它仍在,可能有一种方法可以修复它。
啊,问题是nth被内联扩展为静态方法调用,而不是函数调用, interprener实现的反射静态方法调用没有尝试避免持有头。
...