在Clojure中,为什么向函数定义添加元数据的工作方式不同于其他形式?

在Clojure中,为什么向函数定义添加元数据的工作方式不同于其他形式?,clojure,Clojure,为什么会失败: (eval(带meta'(fn[]0){:stack(gensym“overflow”)}) ; 在(REPL:1:1)处编译时出现语法错误。 ; 无法在此上下文中解析符号:overflow210 当下列各项都没有失败时 (eval(带meta'(do[]0){:stack(gensym“overflow”)}) ; 0 (eval(带meta'(let[]0{:stack(gensym“overflow”)})) ; 0 (eval(带meta'(如果为真01){:stack

为什么会失败:

(eval(带meta'(fn[]0){:stack(gensym“overflow”)})
; 在(REPL:1:1)处编译时出现语法错误。
; 无法在此上下文中解析符号:overflow210
当下列各项都没有失败时

(eval(带meta'(do[]0){:stack(gensym“overflow”)})
; 0
(eval(带meta'(let[]0{:stack(gensym“overflow”)}))
; 0
(eval(带meta'(如果为真01){:stack(gensym“overflow”)})
; 0
(eval(带meta)(println“hello”){:stack(gensym“overflow”)})
; 你好
; 无
上面的例子是我试图找到一个最小的,可重复的例子。我在处理宏时遇到了这个问题,这里有一个简化的示例:

(defmacro my macro[]
(带meta'(fn[]0){:stack(gensym“overflow”)})
(我的宏)
; 在(REPL:1:1)处编译时出现语法错误。
; 无法在此上下文中解析符号:overflow156

在尝试遵循上的这篇文章中解释的模型时,问题在于您使用的是
gensym
,而不是元数据。注意:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(dotest
  (binding [*print-meta* true]
    (let [
          fn-code-plain  '(fn [] 42)
          fn-code-meta   (with-meta '(fn [] 42) {:alpha true})

          fn-code-sym-meta   (with-meta '(fn [] 42) {:alpha (gensym "dummy")})
          ]
      (prn fn-code-plain)
      (prn fn-code-meta)
\
      (prn (eval fn-code-plain))
      (prn (eval fn-code-meta))
      (newline)
      (prn fn-code-sym-meta)

      (throws? (eval fn-code-sym-meta))

      )
    ))
结果:

^{:line 7, :column 27} (fn [] 42)
^{:alpha true}         (fn [] 42)

#object[user$eval19866$fn__19867 0xac891b5 "user$eval19866$fn__19867@ac891b5"]
^{:alpha true} #object[user$eval19870$fn__19871 0x2d1ab7e0 "user$eval19870$fn__19871@2d1ab7e0"]

^{:alpha dummy19865} (fn [] 42)
^{:alpha "Forty-Two!"} (fn [] 42)
^{:alpha "Forty-Two!"} #object[user$eval20082$fn__20083 0x24a63de5 "user$eval20082$fn__20083@24a63de5"]
问题是
eval
看到符号
dummy19865
并试图解决它。它是由gensym创建的这一事实与此无关。关键字没有问题:

  fn-code-kw-meta   (with-meta '(fn [] 42) {:alpha :dummy-42})
  <snip>
  (prn fn-code-kw-meta)
  (prn (eval fn-code-kw-meta))
或定义的符号:

(def mysym "Forty-Two!")
<snip>

  fn-code-mysym-meta   (with-meta '(fn [] 42) {:alpha mysym})
  <snip>

(prn fn-code-mysym-meta)
(prn (eval fn-code-mysym-meta))

总结: 您已经演示了
eval
仅尝试对
fn
表单的元数据进行符号解析,而不尝试其他特殊表单,如
do
let
if
,或使用预先存在的函数,如
println
。如果您希望进一步探索,您可能应该查询Clojure电子邮件列表:

clojure@googlegroups.com 


以上代码基于。

好问题!这很有趣

首先尝试将元数据映射设置为有效的,并在每个示例中检索它,这很有帮助:

(meta (eval (with-meta '(fn [] 0) {:ten 10})))
;;=> {:ten 10}
(meta (eval (with-meta '(do [] 0) {:ten 10})))
;;=> nil
(meta (eval (with-meta '(let [] 0) {:ten 10})))
;;=> nil
(meta (eval (with-meta '(if true 0 1) {:ten 10})))
;;=> nil
(meta (eval (with-meta '(println "hello") {:ten 10})))
;; printed: hello
;;=> nil
希望这能让您对这里发生的事情有所了解:元数据不会作为非-
fn
表单值的一部分返回,因此不会对其进行评估。我们可以用另一个使用其元数据的值(如向量)来检验这一假设:

(meta (eval (with-meta '[1] {:ten 10})))
;;=> {:ten 10}
但是使用
gensym

(eval (with-meta '[1] {:stack (gensym "overflow")}))
;;=> Syntax error compiling at (tmp:localhost:35479(clj)*:25:7).
;;=> Unable to resolve symbol: overflow6261 in this context
您可以看到,搜索
新的MetaExpr
将向您显示元数据评估发出的其他位置(我可以看到向量、映射、集合、函数和
具体化


tl,dr:Clojure评估函数表单的元数据,因为它将评估的元数据附加到结果函数。Clojure语法中支持的数据文本也是如此。表单上的任何其他元数据都会被编译剥离,因此不会对其进行计算,因此不会导致符号解析错误。

我认为在简化示例的过程中,丢失了太多的上下文。我最初在尝试向宏的输出添加元数据时遇到了这个问题(eval没有直接参与)。更多信息,请参阅我的更新帖子!有关完整信息,请参阅更新的答案。另外,为什么在元数据中使用
gensym
?你能用一个简单的字符串吗?谢谢你更新的答案。你最后的总结很好地阐述了当前的情况。我最初是在读了这篇文章之后遇到这个问题的:,发现返回
fn
表单会出现问题,而其他表单则不会。关于测试宏的有趣文章。我通常只测试宏的功能,而不编写单元测试来验证扩展。请看这个答案,我是如何一步一步建立一个宏观的:对这个主题的极好探索!谢谢你走过如何得到答案的过程,我从中学到了很多!
(eval (with-meta '[1] {:stack (gensym "overflow")}))
;;=> Syntax error compiling at (tmp:localhost:35479(clj)*:25:7).
;;=> Unable to resolve symbol: overflow6261 in this context