Clojure 的加载字符串在 repl 中有效,但在 `lein 运行` 中无效

Clojure's load-string works in the repl but not in `lein run`

当我使用 lein repl 启动 repl 时,我可以 运行 函数 greet 并且它按预期工作。

(ns var-test.core
  (:gen-class))

(declare ^:dynamic x)

(defn greet []
  (binding [x "Hello World."]
    (println (load-string "x"))))

(defn -main [& args]
  (greet))

但是如果 运行 通过 lein run 的代码会失败

java.lang.RuntimeException: Unable to resolve symbol: x in this context.

我错过了什么?

是否在编译过程中删除了 var x,尽管已声明,因为它从未在字符串之外使用过?

编辑:

解决方案

@amalloy 的评论帮助我理解我需要绑定 *ns* 以便在预期的命名空间中加载字符串,而不是新的空命名空间。

这按预期工作:

(ns var-test.core
  (:gen-class))

(declare ^:dynamic x)

(defn greet []
  (binding [x "Hello World."
            *ns* (find-ns 'var-test.core)]
    (println (load-string "x"))))

(defn -main [& args]
  (greet))

哇,我以前从没见过这个功能!

根据文档,load-string 旨在从输入字符串中一次一个地读取和加载表单。观察这段代码,由 my favorite template project:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require [tupelo.string :as str]))

(dotest
  (def y "wilma")
  (throws? (eval (quote y)))
  (throws? (load-string "y"))

看来 load-string 从一个新的空环境开始,然后在那个新环境中一次读取和评估一个表单。由于您的 x 不在那个新环境中,因此找不到它并且您收到错误消息。

换一种方式试试:

  (load-string
    (str/quotes->double

      "(def ^:dynamic x)
       (binding [x 'fred']
         (println :bb (load-string 'x'))) " ))

  ;=>  :bb fred

在这种情况下,我们将所有代码作为文本提供给 load-string。它首先读取 evaldef,然后是 binding 和嵌套的 load-string 形式。一切都按预期工作,因为工作环境包含 x.

的 Var

更多代码说明了这一点:

(spy :cc
  (load-string
    "(def x 5)
     x "))

结果

:cc => 5

因此 eval 生成了值为 5 的变量 x,然后对 x 的引用导致生成值 5


令我惊讶的是,部分 load-string 在新的 REPL 中有效:

demo.core=> (def x "fred")
#'demo.core/x
demo.core=> (load-string "x")
"fred"

因此 load-string 必须编码以使用任何预先存在的 REPL 环境作为基础环境。使用 lein run 时,没有可用的 REPL 环境,因此 load-string 从一个空环境开始。