我最近一直在使用Clojure并且直到现在才避免使用宏,所以这是我第一次接触它们。我一直在阅读“掌握Clojure宏”,在第3章,第28页,我遇到了以下示例:
user=> (defmacro square [x] `(* ~x ~x))
;=> #'user/square
user=> (map (fn [n] (square n)) (range 10))
;=> (0 1 4 9 16 25 36 49 64 81)
上下文是作者解释说,虽然简单地将square
宏传递给映射会导致错误(无法获取宏的值),但将其包装在函数中是有效的,因为:
当匿名函数(fn [n](square n))被编译时, square expression得到macroexpanded,为(fn [n](clojure.core / * n N))。这是一个非常合理的功能,所以我们没有 编译器的问题
如果我们假设在运行时(在编译或“定义”时间)评估函数的 body ,从而在运行时之前扩展宏,这对我来说是有意义的。但是,我一直认为函数 bodys 直到运行时才被评估,并且在编译时你基本上只有一个函数对象,它具有一些词汇范围的知识(但不了解它的主体)。 / p>
我在这里明显地混淆了编译/运行时语义,但是当我看到这个示例时,我一直认为在map强制调用之前不会扩展square,因为它位于 body中匿名函数的em>,我思想在运行时才会被评估。我知道我的想法是错误的,因为如果是这种情况,那么n
将绑定到(range 10)
中的每个数字,并且不存在问题。
我知道这是一个非常基本的问题,但事实证明,在我第一次接触时,宏完全包裹我是非常棘手的!
答案 0 :(得分:2)
一般来说,函数体在编译时没有被评估,但是宏总是在编译时被评估,因为它们总是在编译时扩展,无论是否在函数内部。
您可以编写一个扩展为函数的宏,但您仍然无法引用/传递宏,就像它是一个函数一样:
(defmacro inc-macro [] `(fn [x#] (inc x#)))
=> #'user/inc-macro
(map (inc-macro) [1 2 3])
=> (2 3 4)
答案 1 :(得分:1)
defmacro
在编译时被扩展,因此您可以将其视为编译期间执行的函数。这将用“返回”代码替换每次出现的宏“调用”。
答案 2 :(得分:0)
您可以将宏视为语法规则。例如,在Scheme中,宏以define-syntax形式声明。编译器中有一个特殊步骤,在编译代码之前将所有宏调用替换为其内容。因此,您的最终代码中不会有任何square
次调用。比如说,如果你写了像
(def value (square 3))
扩展后的最终版本将是
(def value (clojure.core/* 3 3))
有一种特殊方法可以在扩展后检查宏的主体:
user=> (defmacro square [x] `(* ~x ~x))
#'user/square
user=> (macroexpand '(square 3))
(clojure.core/* 3 3)
这就是为什么宏是一个短暂的东西,它只存在于源代码中,但不存在于它的编译版本中。这就是为什么它不能作为值传递或以某种方式引用。
关于宏的最佳规则是:在你的工作中真正需要它们之前尽量避免它们。