Clojure - (read-string String calling function

3

我有一个Clojure文件,其中包含以下内容:

(ns helloworld
  (:gen-class
    :main -main))

(defn hello-world-fn []
  (println "Hello World"))

(defn -main [& args]
  (eval (read-string "(hello-world-fn)")))

我正在运行它,并且使用以下内容:

lein run helloworld

我遇到了以下错误:

Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol:
 helloworld in this context, compiling:(helloworld.clj:12)

我有一种感觉需要使用ns-resolveresolve来做些什么,但是我没有成功。在主函数中,我尝试过以下方法:

(let [call-string  (read-string "(hello-world-fn)")
      func (resolve  (symbol (first call-string)))
      args (rest call-string)]
   (apply func args))

没有成功。

有人能否(a)指引我正确的方向;(b)解释当发生这种情况时Clojure阅读器中正在发生什么?

2个回答

6

尝试查看你的-main中实际的命名空间。

(defn -main [& args]
  (prn *ns*)
  (eval (read-string "(hello-world-fn)")))

在异常出现之前,它会输出#<Namespace user>。这提示我们使用lein run执行程序时,程序开始于user命名空间,该命名空间显然不包含您的hello-world-fn符号的映射。您需要显式地限定它。

(defn -main [& args]
  (eval (read-string "(helloworld/hello-world-fn)")))

当直接调用(hello-world-fn)时,它可以正常工作。为什么它的行为与eval不同呢? - viebel
因为在这种情况下符号hello-world-fn将在编译时解析,所以源文件顶部附近的命名空间声明是相关的。 另一方面,"(hello-world-fn)"只是一个字符串。 编译器不会查看字符串以对其进行命名空间限定,因为这样做是错误的。 因此,在运行时执行的eval需要通过查看*ns*变量来解析名称引用。 - Matthias Benkard
真的很喜欢调试方法和直接解决问题的方式。我认为另一个方法更具创意和优雅。感谢@MatthiasBenkard解释编译阶段。 - hawkeye

3
你可以通过使用“宏”以非常优雅的方式解决你的问题。事实上,你可以编写一个模仿“eval”的宏。
(defmacro my-eval [s] `~(read-string s))
(my-eval "(hello-world-fn)")); "Hello World"

它比eval更好用,因为s的符号解析发生在调用my-eval的上下文中。感谢@Matthias Benkard的澄清。
您可以在http://clojure.org/reader中了解有关宏及其语法的信息。

为什么这个解决命名空间问题,而ns-resolve却不能? - hawkeye
1
@hawkeye 因为 resolve 在运行时工作,并且将解析当前命名空间中的符号,这是在 *ns* 变量指向的时间内进行的,而文件上下文中宏展开的代码是由文件编译器处理的,根据文件中的命名空间声明解析符号。 - Matthias Benkard
谢谢 - 所以如果在上面的例子中,*ns*已经指向了helloworld,为什么它没有resolve该函数? - hawkeye
@hawkeye 你需要严格区分运行时和编译时。在编译时,正在编译的命名空间是已知的,但是在编译后,代码已经完全解析并命名空间限定,因此独立于运行时的当前命名空间。传递给eval的代码是基于*ns*变量在那个时间点解析的,也就是在运行时而不是在编译时。当您调用在另一个命名空间中编译的函数时,*ns*变量不会自动更改(在我看来这将是相当奇怪的行为)。您必须手动绑定*ns* - Matthias Benkard
更加优雅和灵活。我也非常喜欢@MatthiasBenkard的解释。 - hawkeye

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接