【问题标题】:How does Clojure compile the code that's run in a REPL?Clojure 如何编译在 REPL 中运行的代码?
【发布时间】:2020-09-22 18:00:36
【问题描述】:

诚实的菜鸟问题。根据 Russ Olsen 的 Getting Clojure,我知道以下几点:

(1) Clojure 代码在运行前编译为 JVM 字节码。

(2) Clojure 代码可以在使用函数 (read) 和 (eval) 或等效函数的 REPL 中运行,几乎是即时反馈。

因此,Clojure 代码到 JVM 字节码的编译似乎必须在 REPL 期间的某个时间点发生,大概是在(读取)阶段或之后不久。

但这是一个模糊的心理画面,我想澄清一下。

例如,很高兴知道在 REPL 中代码何时真正被编译,从编译创建的数据如何存储在 RAM 中,然后 由 (eval) 访问,以及在这期间或之后发生的任何重要步骤。

换句话说,我想更详细地了解香肠的真正制作方法:

Clojure 如何编译在 REPL 中运行的代码?

(加分项:这与 Clojure 从非 REPL 源(例如 Leiningen 项目)编译代码时所做的有什么不同?)

【问题讨论】:

    标签: clojure compilation read-eval-print-loop


    【解决方案1】:

    阅读器使用字符并生成 Clojure 数据结构(列表、向量、符号等)。读取阶段肯定对 JVM 字节码一无所知。这发生在 eval 阶段:编译器使用这些数据结构并生成 JVM 字节码。

    在运行 REPL 时,该字节码存储在 DynamicClassLoader 中 - 所有 JVM 类都必须由某个 ClassLoader 定义,而 DynamicClassLoader 是 Clojure 创建的,它允许从 Clojure 数据结构中动态定义类。

    当编译成类文件时,相同的字节码会简单地以 .class 文件的形式写入磁盘,之后可能会打包到 jar 中。

    【讨论】:

    • 由于编译严格发生在 (eval) 期间,我认为宏在 (read) 之后但在 (eval) 之前得到扩展是否正确?
    • 宏扩展是编译器执行的一个步骤。如果它想要评估一个表单,并检测到该表单代表一个宏的使用,它会在继续之前扩展宏。
    【解决方案2】:

    一个简短的测试揭示了答案:

    (ns tst.demo.core
      (:use tupelo.core tupelo.test))
    
    (defmacro with-trace
      [tag & forms]
      (println "*** running macro ***   tag:  " tag)
      `(do
         (println ~tag :enter)
         (let [result# (do ~@forms)]
           (println ~tag :leave)
           result#)))
    
    (dotest
      (newline)
      (println :answer
        (with-trace :add-5
          (+ 2 3)))
    
      (newline)
      (println :using-eval
        (eval
          (quote (tst.demo.core/with-trace :the-ultimate-answer
                   (clojure.core/inc 41))))))
    
    

    哪个打印:

    > lein clean ; lein test
    
    *** running macro ***   tag:   :add-5
    
    lein test _bootstrap
    
    -------------------------------
       Clojure 1.10.1    Java 14
    -------------------------------
    
    lein test tst.demo.core
    
    :add-5 :enter
    :add-5 :leave
    :answer 5
    
    *** running macro ***   tag:   :the-ultimate-answer
    :the-ultimate-answer :enter
    :the-ultimate-answer :leave
    :using-eval 42
    

    所以我们看到eval 确实“评估”宏以及函数。


    更新

    @amalloy 是正确的,因为我忘了引用我发送到 eval 的代码,所以宏 with-trace 是在编译时而不是运行时评估的。在这个版本中,发送到eval 的代码被引用,所以它只是一个包含 2 个符号、1 个关键字和一个整数的嵌套列表。现在,eval 看到宏,运行它以生成实际代码,然后编译并运行宏输出。

    【讨论】:

      猜你喜欢
      • 2021-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-26
      • 2013-12-26
      相关资源
      最近更新 更多