Clojure宏符号评估

时间:2017-05-16 05:55:22

标签: clojure functional-programming macros

我对如何在宏内部评估符号感到困惑。我尝试了以下示例

(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

那么这里实际发生了什么,为什么在语法引用的情况下抛出异常?

1 个答案:

答案 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的内容替换。