从Clojure中的另一个名称空间调用私有宏吗?

时间:2018-08-16 07:12:42

标签: clojure

在一个文件中提供以下内容:

(ns demo.first)
(defmacro ^:private private-macro [a] a)

以及其他文件中的以下内容:

(ns demo.second
  (:require [demo.first :as first]))
(first/private-macro 10)

在demo.second中对私有宏的调用将抛出:var: #'demo.first/private-macro is not public,正如我所期望的。

现在,有没有一种方法可以使该调用成功,而无需将宏公开?

对于功能,我可以这样做:

(#'first/private-macro 10)

但是使用宏,它会抛出:Wrong number of args (1) passed to: first/private-macro

我希望对该私有宏进行单元测试,个人更喜欢使用私有元数据而非impl名称空间。这就是为什么我希望对此有解决方案的原因。

谢谢。

更新:

我发现,由于defmacro本身是一个宏,因此它首先扩展为为宏创建符号和Var并将其元数据添加到其中的形式。

因此:

(defmacro ^:private private-macro [a] a)

第一个由defmacro宏处理,并扩展为:

(do
  (clojure.core/defn ^{:private true} private-macro
    ([&form &env a] a))
  (. (var ^{:private true} private-macro)
     ^{:line 487, :column 49}
     (setMacro))
  (var ^{:private true} private-macro))

如您所见,然后发生的是:

  1. private-macro声明了defn fn,并将其设置为私有。
  2. 此函数接受3个参数[&form &env a]这就是为什么在使用#'调用宏时,我们得到错误数量的参数(1)异常的原因。
  3. 通过调用private-macro方法将setMacro变量设置为宏。
  4. 返回private-macro变量。

本质上,发生的是,如果您调用private-macro变量所指向的函数,例如使用(#'private-macro)语法时,实际上就是在调用该函数。参见上文,其中包含3个参数。如果您的宏本身带有多个参数,则该函数将占用2 +宏的args数量。

所以我仍然不知道如何调用私有宏:

起初,我认为用nil剔除&form&env会起作用:

(#'first/private-macro nil nil 10)

对于上面的我的简单宏,它确实会返回10。但是在更复杂的宏上,它不需要进一步扩展,所以不需要,而是让我将宏扩展返回给我?!?

然后我想我可以在调用宏之前临时使用alter-meta!从宏中删除私有元数据。因此:

(alter-meta! #'first/private-macro
             (fn [meta] (dissoc meta :private)))
(first/private-macro 10)
(alter-meta! #'first/private-macro
             (fn [meta] (assoc meta :private true)))

但这仅适用于REPL。之后再尝试编译代码,似乎编译器本身会抛出var: #'demo.first/private-macro is not public错误,甚至在alter-meta!有机会运行之前也是如此,从而导致编译失败。

我真的不知道为什么#'与普通的宏调用不同,为什么将nil传递给&form&env却不起作用不适用于所有宏。以及如何使alter-meta!在编译时起作用。因此,如果有人知道,请回答!

1 个答案:

答案 0 :(得分:2)

  

对于上面的我的简单宏,它确实会返回10。但是在更复杂的宏上,它不需要进一步扩展,所以不需要,而是让我将宏扩展返回给我?!?

是的。如您所知,当您编写(defmacro m [x] (list x x))时,您:

  1. 定义一个函数m,该函数使用表单作为输入并生成表单作为输出
  2. 告诉编译器寻找诸如(m a)之类的调用,并将其替换为在编译时调用m函数的结果

相反,通过调用#'m,可以跳过步骤2:没有对宏m的调用,因此编译器不会在编译时调用它,也不用结果替换调用代码。由于#'m只是一个将代码作为输入并生成代码的常规函数​​,因此当您绕过特殊的编译器行为并在运行时调用它时,您当然会得到代码(您无法做很多事情)因为它已经在运行时了)。

不过,好消息是:几乎没有什么令人信服的理由将宏设为私有,因为让其他名称空间调用它不会造成任何危害。私有宏所做的所有事情都是将其扩展为客户端本来可以手动编写的代码。因此,如果您控制此宏,则可能应该将其公开。如果不这样做,那么您可以编写宏将为您编写的任何代码。

如果您绝对坚持要调用其他人的私有宏,则可以通过以下方式拆分部分(1)和(2):定义自己的宏,其实现将功能委托给支持另一方的私有var的函数。命名空间:

(defmacro cheat [& args]
  (apply #'m &form &env args))

由于cheat是您自己的宏,因此您可以按通常的方式调用它,并使用编译器的“在编译时调用”机制。然后,您委托给生成所需代码的函数,并显式传递&form&env