关于Clojure命名空间和宏的问题

6
假设我有一堆命名空间(比如apple、banana和orange)。在这些命名空间中,我使用了"eat"宏,该宏调用(而不是生成)"peel"函数。每种水果的"peel"函数都不同,但是宏是相同且相当庞大的,因此我想创建一个"fruit"命名空间,其中包含"eat"宏。但是,当我从"apple"命名空间调用"eat"宏时,"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)

强调一下,这意味着只有在宏被展开时才应该调用peel函数,就像这个例子。

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

那么,如何将宏与多态结合起来呢?
4个回答

7
您所描述的并不是多态,而是所谓的“本地捕获”。您希望“eat”宏可以“捕获”“peel”的本地定义。
在大多数Lisp语言中,特别是Clojure中,这被认为是不好的风格,因为它可能导致微妙和不可预测的错误。
更好的解决方案是在调用“eat”宏时传递正确的“peel”。
(ns fruit)
(defmacro eat [peeler] `(~peeler))

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

如果您真的想进行本地捕获,您可以在宏中使用~'(取消引用-引用)来强制执行:

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

这不太是我的意思。我希望在展开时间调用“peel”函数。我已经澄清了问题。 - Michiel de Mare
我明白了,会提供另一个答案。 - Stuart Sierra

3
正如编辑后的问题所解释的那样,这与本地捕获略有不同,因为您不是在宏扩展中使用peel,而是在宏执行中使用。
这很困难,因为宏不评估其参数。即使将peel作为参数传递给eat,在宏体内部它只是一个符号,而不是可调用的函数。
实现您想要的唯一方法(不使用eval)是在编译时解析该符号。
(defmacro eat []
   ((var-get (resolve 'peel)))
   ... return the expansion of "eat" ...)

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

不用说,这是一个非常不寻常的设计,可能需要重新考虑。


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

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

1

编辑:对不起,我已经发布了以下内容。但是你说的是“调用,而不是生成”peel函数。因此,我所写的可能不是你想要的,尽管它似乎会获得预期的结果。

对我来说,简单引用(peel)就可以了。

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

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

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

谢谢,但实际上不是我想要的。这确实可以在这里得到期望的结果,但在我的实际用例中不行。 - Michiel de Mare

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