在一个文件中给出以下内容:
(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
本身就是一个宏,所以它首先扩展成形式,创建符号和宏的变量,并将其元数据添加到其中。因此:
(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))
如您所见,接下来的情况如下:
- 使用
defn
声明了一个名为private-macro
的函数,并将其设置为私有。 - 该函数接受三个参数
[&form &env a]
,这就是当使用#'
调用宏时出现错误参数数量(1)异常的原因。 - 通过调用其
setMacro
方法,将private-macro
变量设置为宏。 - 返回
private-macro
变量。
本质上,发生的事情是,如果您调用由 private-macro
变量指向的函数,例如使用 (#'private-macro)
语法的情况下,实际上您正在调用上述函数,该函数接受三个参数。 如果您的宏本身需要多个参数,则该函数将接受2 + 您的宏的参数数量。
所以我仍然不知道如何调用私有宏:
起初,我认为使用 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!
生效。 因此,如果有人知道,请回答!