定义Clojure宏语法

Sta*_*ked 13 clojure

我定义了一个unless宏如下:

user=> (defmacro unless [expr body] (list 'if expr nil body))
#'user/unless
user=> (unless (= 1 2) (println "Yo"))
Yo
Run Code Online (Sandbox Code Playgroud)

你可以看到它工作正常.

现在,在Clojure中,可以通过两种方式定义列表:

; create a list
(list 1 2 3)

; shorter notation
'(1 2 3)
Run Code Online (Sandbox Code Playgroud)

这意味着unless可以在没有list关键字的情况下编写宏.但是,这会导致抛出Java异常:

user=> (unless (= 1 2) (println "Yo"))
java.lang.Exception: Unable to resolve symbol: expr in this context
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么会失败吗?

sep*_*p2k 15

'(foo bar baz)不是捷径(list foo bar baz),它是捷径(quote (foo bar baz)).虽然列表版本将返回包含变量foo,bar和baz的值的列表,但版本'将返回包含符号foo,bar和baz的列表.(换句话说'(if expr nil body)是相同的(list 'if 'expr 'nil 'body).

这会导致错误,因为引用的版本宏扩展为(if expr nil body)而不是(if (= 1 2) nil (println "Yo"))(因为它不是将宏的参数替换为expr和body,而是返回名称expr和body(在扩展中它们被视为不存在的变量)码).

在宏定义中有用的快捷方式是使用`.`类似的工作'(即它引用它后面的表达式),但它允许您通过使用来评估一些未引用的子表达式~.例如,您的宏可以重写为(defmacro unless [expr body] `(if ~expr nil ~body)).这里重要的是,expr并且body没有引用~.这样,扩展将包含它们的值,而不是字面上包含名称exprbody.