定义Clojure宏语法

13

我定义了一个名为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

能有人解释一下为什么这个会失败吗?


6
FYI,Clojure核心库中已经有类似的宏,称为 when-notif-not - Brian Carper
1个回答

15
'(foo bar baz)'不是'(list foo bar baz)'的缩写,它是'(quote (foo bar baz))'的缩写。虽然带list版本将返回一个包含变量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'。

此外,\``会确保宏的卫生性。list`绝不能用于定义宏扩展。 - kotarak
对于这个宏本身的简短注释:你可以支持隐式的 do,以允许在宏体中使用多个表单。(defmacro unless [expr & body] \(if ~expr nil (do ~@body)))` - kotarak
1
如果大多数表格都需要转义,使用列表是很好的选择;例如(defmacro my-if [pred then else] \(if ~pred ~then ~else))(defmacro my-if [pred then else] (list `if pred then else))`。 - Sam Ritchie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接