请在2024 Clojure调查问卷中分享您的想法!

欢迎!有关本站运作的更多信息,请参阅关于页面。

+1投票
Sequences

在随机玩大斐波那契数时,我发现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中的“简单”表达式与“复杂”表达式之间的差异。

对于看似足够简单(例如,值调用其他函数的def)的表达式,eval实际上就像一个解释器,不会发出jvm字节码并运行。在这里,保留头部的是解释器代码路径,它比生成的字节码要简单得多。

let表达式,函数以及除字面值或带有简单参数的函数调用之外的所有内容,都是足够复杂的,它们会被编译成字节码,具备更仔细的参数处理。

对于“简单表达式”的行为是一种优化还是必须的?
by
难以确定,通常被描述为一种优化。Clojure最初针对Java5,这在与创建很多小类相关的方面有显著的成本(如果需要生成字节码并执行它,它必须位于一个类中)。如今Java在这一方面已经有一些改进。因此,始终生成字节码可能就足够了。

解释器的代码看起来实际上正在尝试避免持有头对象,但不知何故它还是持有了,也许有办法可以修复这个问题。
by
啊,问题在于nth被内联展开为静态方法调用,而不是函数调用,解释器执行的反射静态方法调用并没有尝试避免持有头对象。
...