无法理解Clojure中的一个简单宏 - 将宏作为替代函数传递给高阶函数进行映射

时间:2018-01-31 01:18:56

标签: clojure macros

我最近一直在使用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中,我思想在运行时才会被评估。我知道我的想法是错误的,因为如果是这种情况,那么n将绑定到(range 10)中的每个数字,并且不存在问题。

我知道这是一个非常基本的问题,但事实证明,在我第一次接触时,宏完全包裹我是非常棘手的!

3 个答案:

答案 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)

这就是为什么宏是一个短暂的东西,它只存在于源代码中,但不存在于它的编译版本中。这就是为什么它不能作为值传递或以某种方式引用。

关于宏的最佳规则是:在你的工作中真正需要它们之前尽量避免它们。