我将unless
宏定义如下:
user=> (defmacro unless [expr body] (list 'if expr nil body))
#'user/unless
user=> (unless (= 1 2) (println "Yo"))
Yo
你可以看到它运作良好。
现在,在Clojure中,可以通过两种方式定义列表:
; create a list
(list 1 2 3)
; shorter notation
'(1 2 3)
这意味着可以在没有unless
关键字的情况下编写list
宏。但是,这会导致抛出Java异常:
user=> (unless (= 1 2) (println "Yo"))
java.lang.Exception: Unable to resolve symbol: expr in this context
有人可以解释为什么会失败吗?
答案 0 :(得分: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
不加引号~
。这样,扩展将包含其值,而不是字面上包含名称expr
和body
。