在概念层面,LISP(和方言)中的宏取一段代码(作为列表)并返回另一段代码(再次作为列表).
基于以上原则,一个简单的宏可以是:
(defmacro zz [a] (list print a))
;macroexpand says : (#<core$print clojure.core$print@749436> "Hello")
Run Code Online (Sandbox Code Playgroud)
但在clojure中,这也可以写成:
(defmacro zz [a] `(print ~a))
;macroexpand says : (clojure.core/print "Hello")
Run Code Online (Sandbox Code Playgroud)
我不确定这里的区别,哪些应该是首选方式.第一个看起来很简单,因为我们应该返回列表并避免使用像back tick这样的奇怪字符.
没有人指出这一点......你的2个宏之间的区别是:你的第二个形式(使用反引号)
(defmacro zz [a] `(print ~a))
Run Code Online (Sandbox Code Playgroud)
相当于:
(defmacro zz [a] (list 'print a))
Run Code Online (Sandbox Code Playgroud)
这与你的第一个例子不同:
(defmacro zz [a] (list print a))
Run Code Online (Sandbox Code Playgroud)
请注意缺少的单引号 - 这就是您的宏扩展不同的原因.我同意其他人的帖子:如果你的宏有一个相当简单的"形状",使用反引用更常规.如果你必须进行代码遍历或动态构造(即复杂的宏),那么使用列表并构建它通常就是完成的.
我希望这种解释是有道理的.
在某种程度上,明确构建列表是"最简单的",因为您需要知道的核心概念很少:只需接受一个列表并更改它,直到您有一个新列表.Backtick是"模板化"代码块的便捷快捷方式; 可以在没有它的情况下编写任何宏,但对于任何大宏,它很快就会变得非常不愉快.例如,考虑两种写let宏方式fn:
(defmacro let [bindings & body]
(let [names (take-nth 2 bindings)
vals (take-nth 2 (rest bindings))]
`((fn [~@names]
(do ~@body))
~@vals)))
(defmacro let [bindings & body]
(let [names (take-nth 2 bindings)
vals (take-nth 2 (rest bindings))]
(cons (list `fn (vec names) (cons `do body))
vals)))
Run Code Online (Sandbox Code Playgroud)
在第一种情况下,使用反引号可以清楚地表明,您正在编写包含正文的名称的函数,然后使用值调用它 - 宏代码与扩展代码"形状"相同,因此您可以想象它会是什么样子.
在第二种情况下,只有cons和list所有的地方,它是一个真正的头痛摸出扩张将是什么样子.当然,情况并非总是如此:有时候如果没有反复的话就可以更清楚地写一些东西.
Kyle Burton提出了另一个非常重要的观点:print不一样'print!您的宏扩展应包含符号 print,而不是其值(这是一个函数).在代码中嵌入对象(例如函数)非常脆弱,只能偶然使用.因此,请确保您的宏扩展为您自己实际编写的代码,并让评估系统执行繁重的工作 - 您可以键入符号print,但无法键入指向函数当前值的指针print.
它们之间存在风格差异.您的示例非常简单,但在更复杂的宏中,差异会更大.
例如,"Clojure的喜悦"一书中定义的除非宏:
(defmacro unless [condition & body]
`(if (not ~condition)
(do ~@body)))
Run Code Online (Sandbox Code Playgroud)
从书中:
Syntax-quote允许以下if-form作为表达式的一种模板,表示宏在扩展时的任何使用.
创建宏时,始终选择最具可读性和惯用性的样式.
相比之下,上面的代码可以等效地编写:
(defmacro unless [condition & body]
(list 'if (list 'not condition)
(list* 'do body)))
Run Code Online (Sandbox Code Playgroud)