我对如何在宏内部评估符号感到困惑。我尝试了以下示例
(defmacro fx-bad
[f x]
`(f x))
(defmacro fx
[f x]
`(~f ~x))
(let [f inc x 1] (fx f x)) ;-> 2
(let [f inc x 1] (fx-bad f x)) ;-> exception
fx
宏函数正确,而fx-bad
抛出异常
CompilerException java.lang.RuntimeException: No such var: user/f, compiling:(/tmp/form-init2718774128764638076.clj:12:18)
符号是否在宏内部解析?为什么fx-bad
不起作用,fx
呢?
-
修改
显然,该例外与名称空间有关。实际上在宏中没有曾经评估过任何参数。语法引用中的~
只生成传递给宏的实际字符串(Symbol),没有它,列表中的符号将按原样返回。
有趣的是,如果提供给宏调用的参数和引用(非语法引用)列表中的符号具有相同的名称,则它们不必是不引用的,它们无论如何都是相同的符号。这是一个很好的指示,宏在评估之前是如何发生的,只是操纵原始符号,这在此时并不意味着什么。
然而,由于语法引用的情况不同,并且在符号未加引号之前抛出异常,甚至认为扩展的宏看起来像评估者要评估的有效代码行。以下是一些例子
(defmacro fx
[f x]
`(~f ~x))
(defmacro fx-bad
[f x]
'(f x))
(defmacro fx-very-bad
[f x]
`(f x))
`(let [f inc x 1] ~(macroexpand '(fx f x)))
`(let [f inc x 1] ~(macroexpand '(fx-bad f x)))
`(let [f inc x 1] ~(macroexpand '(fx-very-bad f x)))
(macroexpand '(fx (fn [a] a) b))
(macroexpand '(fx-bad (fn [a] a) b))
(macroexpand '(fx-very-bad (fn [a] a) b))
(let [f inc x 1] (fx f x)) ;-> 2
(let [ff inc xx 1] (fx ff xx)) ;-> 2
(let [f inc x 1] (fx-bad f x)) ;-> 2
;(let [ff inc xx 1] (fx-bad ff xx)) ;-> exception
;(let [f inc x 1] (fx-very-bad f x)) ;-> exception
-
=> #'user/fx
#'user/fx-bad
#'user/fx-very-bad
(clojure.core/let [user/f clojure.core/inc user/x 1] (f x))
(clojure.core/let [user/f clojure.core/inc user/x 1] (f x))
(clojure.core/let [user/f clojure.core/inc user/x 1] (user/f user/x))
((fn [a] a) b)
(f x)
(user/f user/x)
2
2
2
那么这里实际发生了什么,为什么在语法引用的情况下抛出异常?
答案 0 :(得分:1)
请参阅Clojure for the Brave & True和其他大多数Clojure书籍中的完整详情。
基本上,反引号创建一个模板,代字号告诉编译器要替换哪些值。在Groovy,bash,&其他语言,就像在字符串中替换变量一样:
f = "+";
x = 1;
y = 2;
result = "(${f} ${x} y)"
result => "(+ 1 y)"
在此示例中,y
未被替换。 Clojure宏等价物将是:
(let [f '+
x 1
y 2]
`(~f ~x y) )
;=> (+ 1 y)
由于y
不是由“代号”“未引用”,因此它是字面上的,不会被变量y
的内容替换。