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

Posted

技术标签:

【中文标题】Clojure 的加载字符串在 repl 中有效,但在 `lein run` 中无效【英文标题】:Clojure's load-string works in the repl but not in `lein run` 【发布时间】:2021-11-27 01:03:09 【问题描述】:

当我使用 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))

【问题讨论】:

在 Stack Overflow 上,回答你自己的问题完全没问题。但请在新的答案中这样做,而不是在编辑您的问题时这样做。 ***.com/help/self-answer 在 SO 上为您的问题添加您自己的答案非常好 【参考方案1】:

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

根据文档,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 生成了 var x,其值为 5,然后对 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以空环境开头。

【讨论】:

区别在于*ns*绑定在repl中,但通过-main运行时没有。 load-string 对 repl 一无所知,它只是 evals 一次形成一个。 感谢 Alan Thompson 的详尽回答。通过@amalloy 的最后评论,我了解了如何解决问题,并为我的问题添加了解决方案。

以上是关于Clojure 的加载字符串在 repl 中有效,但在 `lein run` 中无效的主要内容,如果未能解决你的问题,请参考以下文章

无法在spacemacs cider(Clojure)中切换到repl

在 Clojure 中使用 repl 而不是 println

markdown Clojure中Java类的漂亮打印方法。在REPL中有用。

Clojure 的 Emacs/Swank/Paredit 教程

Clojure 在函数中减少溢出,但在直接传递给 REPL 时不会溢出

Clojure - 在没有project.clj的情况下启动REPL