关于clojure名称空间和宏的问题

时间:2010-02-25 17:52:06

标签: macros clojure

假设我有一堆命名空间(苹果,香蕉,橙色)。在这些命名空间中,我使用eat宏,它调用(不是“生成”,调用peel函数。 peel函数对于每个水果都是不同的,但宏是相同的,而且相当大,所以我想创建一个包含fruit宏的eat命名空间。但是,当我从eat命名空间调用apple宏时,eat宏应调用apple/peel函数。

为了说明(但这不起作用):

(ns fruit)
(defmacro eat [] (peel))

(ns apple)
(defn peel [] (prn "peeled apple"))
(fruit/eat)

(ns banana)
(defn peel [] (prn "peeled banana"))
(fruit/eat)

要强调,这意味着只有在扩展宏时才应调用剥离函数,如本例所示。

(ns apple)
(defn peel [] (prn "peeled apple"))
(defmacro eat [] (peel))
(macroexpand-1 '(eat))

那么,关于如何组合宏和多态的任何想法?

4 个答案:

答案 0 :(得分:7)

您所描述的不是多态,而是所谓的本地捕获。您希望 eat 宏“捕获” peel 的本地定义。

在大多数Lisp中,这被认为是不好的风格,尤其是Clojure,因为它可能导致细微且不可预测的错误。

更好的解决方案是在调用时将正确的剥离传递给 eat 宏:

(ns fruit)
(defmacro eat [peeler] `(~peeler))

(ns apple)
(defn peel [] (prn "Peeled an apple"))
(fruit/eat peel)

如果你真的想进行本地捕获,可以在宏中强制使用〜'(unquote-quote):

(ns fruit)
(defmacro eat [] `(~'peel))

答案 1 :(得分:3)

正如编辑问题中所解释的那样,这与本地捕获略有不同,因为你没有在宏扩展中使用 peel ,而是在宏本身的执行中使用。

这很难,因为宏不会评估他们的论点。即使你将 peel 作为参数传递给 eat ,在宏的主体内它只是一个符号,而不是一个可调用的函数。

执行所需操作(不使用 eval )的唯一方法是在编译时解析符号:

(defmacro eat []
   ((var-get (resolve 'peel)))
   ... return the expansion of "eat" ...)

resolve 函数接受一个符号并返回它在当前命名空间中映射到的Var。获得Var后,可以使用 var-get 检索实际函数(Var的值)。额外的括号集调用该函数。

毋庸置疑,这是一个非常不寻常的设计,可能需要重新考虑。

答案 2 :(得分:2)

(defmacro eat [] ((var-get (resolve 'peel))))

请注意,您滥用命名空间。

答案 3 :(得分:1)

编辑:抱歉。我发布了以下内容。但你说的是“调用,而不是生成”剥离功能。所以我写的可能不是你想要的,虽然看起来它会得到预期的结果。

简单引用(peel)为我工作。

(ns fruit)
(defmacro eat [] '(peel))

(ns apple)
(defn peel [] (prn "peeled apple"))
(fruit/eat)

(ns banana)
(defn peel [] (prn "peeled banana"))
(fruit/eat)