2024 Clojure状态调查!分享您的想法。

欢迎!请查看关于页面以获取更多关于如何工作的信息。

+1
头像 编译器

考虑这个基本项目

(ns whatever.core
  (:gen-class))

(def some-var (doto (System/getenv "FOO") println))

(defn -main [& args] (println some-var))

如果我从这个项目中制作出 uberjar,我可以看到在编译过程中,会打印出 nil (预料之中)。然而,当我使用环境变量运行它时,我期望能看到 nil,但我看到了正确的值打印出来,并且 some-var 实际上被重新绑定!尽管这听起来有点“方便”(对于这个用例),但我却无法理解为什么会这样……既然我已经 AOT 了命名空间,我预计必须将获取环境变量的操作延迟到运行时(例如,通过 delay 或其他方式),以便进行此操作。双倍加载的影响是什么?如果我在某个 def 中有一些非常昂贵的计算,我真的只想预先计算一次(并且只计算一次)怎么办?据我所知,当程序启动时,无论是否 AOT,所有可通过 -main 访达的 def 表达式都会重新加载,请帮助我理解这一点,因为它让我狂躁不安!

提前感谢……

1 答案

+1
头像

没有双重加载或重新加载。Clojure 中代码的实际编译实际上运行了那段代码(这意味着,它会运行所有顶层形式,除非在顶层调用,否则实际上不会运行您的 -main 函数)。在 uberjar 中运行代码也是如此,也会运行代码。这是一个完全独立的步骤,独立加载。

双倍加载的影响是什么?

主要影响是,命名空间声明应该不含副作用并且是引用透明的(除了所有 def 可能会隐式地改变当前命名空间)。

如果在def中有一个非常昂贵的计算,并且真的只想预先计算一次(且只有一次),会怎么样?

如果您不想在编译时运行这个昂贵的计算,可以使用delay
如果您想在编译期间内联计算的输出,可以使用宏。

> 在uberjar中运行代码,嗯,也会运行代码

但执行应该从"-main"方法开始,不是吗?为什么变量会重新绑定?这是因为根绑定是null,所以它们被认为是未绑定的,这导致表达式被重新评估吗?

> 如果不想在编译时运行这个昂贵的计算,可以使用延迟。

是的,当然,但这已经偏离了重点——我不应该能够用普通的`def`做吗?比如说,我正在计算第n个斐波那契数,这需要几秒钟。为什么我还要在编译和运行时都付出这个代价?当我有AOT编译时,这显然没有意义,我在跟着什么走istisch?
> 但执行应该从"-main"方法开始,不是吗?

是的,主功能是执行的。运行所有`ns`和`def`形式也是执行。运行任何其他顶级代码也是执行。Clojure的编译和执行是相互交织的——这与许多其他语言处理方式不同。这是我们拥有出色的REPL体验的原因之一,其中“E”部分与从开始就在某个uberjar中有相同的代码难以区分。相反,在正常情况下运行编译后的工件将不会与通过REPL逐行按正确顺序运行原始代码不同。

> 为什么变量会重新绑定?

它们并不是真正的重新绑定。它们只是进行了绑定。uberjar编译过程和使用该uberjar的过程是两个不同的过程,各有不同的运行时版本的同一样变量。这与变量的值无关。

可能是本节第二段的内容有所帮助:https://clojure.org/reference/evaluation

> 假设我正在计算第n个斐波那契数,这个过程需要几秒钟。为什么我需要在编译和运行时都付出这个代价?

首先,计算任何N的斐波那契数不应该需要几秒钟。 :) 但这已经偏离了重点,当然。
“为什么”这个问题没有直接的答案,因为这并不是一个像“是的,必须支付两次,用户必须忍受”这样的故意决策。这仅仅是因为Clojure的编译/评估方法的附带结果。

大部分时间,这类事情完全不重要,因为所有顶层代码都是简单的引用透明`def`,计算所需时间几乎没有。
如果由于某种原因,在非常具体的情况下不适用,我也提到了一些解决方案 - `delay`和宏。
by
所以,您的意思是直到运行时才获取环境变量(如下所示)是完全不必要的吗?

>  (def some-var (delay (System/getenv "FOO")))
by
如果编译项目的环境不会影响编译本身,则没有必要。一个相反的例子 - 您可以在编译时将`some-var`设置为某个环境变量的值,然后在宏中使用`some-var`来确定如何展开某些内容。当然,这非常高风险,不推荐这样做。
by
非常感谢您的时间和帮助 - 非常感激:)
...