【问题标题】:Will the clojure compiler automatically evaluate expressions of literals at compile time?clojure 编译器会在编译时自动评估文字表达式吗?
【发布时间】:2010-08-18 22:02:52
【问题描述】:

这可能是一个愚蠢的问题,但是:

假设一个表达式只依赖于字面量,或者其他也只依赖于字面量的表达式;编译器会在编译时评估这个吗?

假设我有,

(def a (some-time-consuming-function some-literal))

(def b (some-other-time-consuming-function a))

是否会在编译时对 b 和 a 进行完全评估,从而不影响用户?

编辑:非常感谢,所有的答案都非常有帮助。

EDIT 6.6.2011: 结果表明,如果您尝试使用这种技术来预先计算一个非常大的数据结构,那么很容易使类文件太大而无法加载。在这些情况下,您希望创建一个将被读入的文件,而不是一个将被加载的类文件。这些答案中描述的宏技巧只能在返回值不是一个过大的结构的情况下应用。

抛出的错误是:“java.lang.ClassFormatError: Invalid this class index” 有关相关情况的讨论,请参阅this thread

【问题讨论】:

    标签: compiler-construction clojure


    【解决方案1】:

    一点都不傻,我不得不考虑并测试一下。

    只有当你使用宏而不是函数时它才会起作用,因为宏的主体是在编译/宏扩展时评估的。例如:

    (defmacro 预处理 [f & args] (让 [x# (apply (resolve f) args)] `~x#)) (def a (预处理一些耗时的函数一些字面量)) (def b (预处理一些其他耗时的函数 a))

    那么abdef'd 到从评估preprocess 返回的值。

    【讨论】:

    • 我认为宏可能是解决这个问题的一种方法。三个问题: 1. 你是如何进行测试的? (我考虑过使用 Ackerman 函数,并查看在该过程中的哪个时间点我注意到速度变慢,但我不知道这样做的最严格的方法是什么。) 2. 何时评估 b —— 在加载时? 3. 语法引号和取消引号符号是否相互抵消?无论如何,非常感谢您的回答!
    • 1) 我使用不同的指标对其进行了测试,即一个名为 println 的函数。该消息在编译期间出现,但在加载已编译的命名空间时不出现,因此可以断定该函数没有在运行时运行。 2) 分配给 var b 的值在宏展开期间进行评估。 3) 是的。它被错误地从早期的迭代中遗留下来。
    • 需要明确的是,您的问题的答案是“否”。只是有办法在你想要的时候实现这种行为。
    【解决方案2】:

    首先,defs 的右侧需要在加载时进行评估有一个非常重要的原因:它们可能以某种方式依赖于环境,在一般情况下,无法判断它们是否这样做或不。举个例子

    (def *available-processors* (.availableProcessors (Runtime/getRuntime)))
    

    其次,这是一种测试实际情况的方法:

    1. 使用 Leiningen 创建一个测试项目 -- 例如,lein new testdefs

    2. :main testdefs.core 放入project.clj

    3. 将以下内容放入src/testdefs/core.clj

      (ns testdefs.core
        (:gen-class))
      
      (defn take-your-time [t]
        (printf "Taking my time (%d)...\n" t)
        (Thread/sleep t))
      
      (def a (take-your-time 5000))
      
      (defmacro frozen-def [v e]
        (let [val (eval e)]
          `(def ~v ~val)))
      
      (frozen-def b (take-your-time 5000))
      
      (defn -main [& args]
        (println "Starting...")
        (println a)
        (println b))
      
    4. 运行lein uberjar;果然,代码要花两次时间。

    5. 运行java -jar testdefs-1.0.0-SNAPSHOT-standalone.jar;你会注意到代码只需要一次。

    【讨论】:

    • 对不起,代码中的奇怪间距 - 看起来像一些 SO Markdown 怪异或不太好的 Markdown-fu 对我来说(第二种可能性似乎更有可能)。
    • 我不敢相信我忘了还有睡眠功能。
    【解决方案3】:

    在您的示例中,耗时的函数仅在加载代码时调用一次。

    Clojure 编译器不会尝试优化常量表达式,但 Java JIT 编译器在某些情况下可能会这样做。

    【讨论】:

      【解决方案4】:

      对于给出的示例,表达式在编译/加载时进行评估。 def 总是 评估它的第二个参数。来自special forms

      (def symbol init?)
      

      使用名称为symbol 和当前命名空间*ns* 的值的命名空间创建并实习或定位一个全局变量。 如果提供了init,则对其进行评估,并将 var 的根绑定设置为结果值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-11-11
        • 1970-01-01
        • 2012-12-26
        • 2014-06-16
        • 1970-01-01
        • 1970-01-01
        • 2011-09-27
        • 2015-05-16
        相关资源
        最近更新 更多