在一个文件中给出以下内容:
(ns demo.first)
(defmacro ^:private private-macro [a] a)
另一个文件中的以下内容:
(ns demo.second
(:require [demo.first :as first]))
(first/private-macro 10)
对 demo.second 中私有(private)宏的调用将抛出:var: #'demo.first/private-macro is not public
,正如我所料。
现在,有没有办法让这个调用成功,而不需要公开宏?
对于功能,我可以这样做:
(#'first/private-macro 10)
但是使用宏时,它会抛出:传递给:first/private-macro的参数数量 (1) 错误
。
我希望对这个私有(private)宏进行单元测试,并且个人更喜欢使用私有(private)元而不是 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))
如您所见,接下来发生的事情是:
private-macro
fn 使用defn
声明,并设置为 private。- 此函数需要 3 个参数
[&form &env a]
。 这就是为什么我们在使用#'
调用宏时会得到错误的参数数量(1)异常。 - 通过调用
setMacro
方法将private-macro
变量设置为宏。 - 返回
private-macro
var。
本质上,发生的情况是,如果您调用 private-macro
var 指向的函数,例如使用 (#'private-macro) 时的情况
语法,您实际上是在调用上面看到的函数,该函数需要 3 个参数。如果您的宏本身采用多个参数,则该函数将采用 2 + 宏的参数数量。
所以我还是不知道如何调用私有(private)宏:
起初我认为用 nils 删除 &form
和 &env
会起作用:
(#'first/private-macro nil nil 10)
对于上面的简单宏,它确实如此,并返回 10。但是对于需要进一步扩展的更复杂的宏,它不会,而是我得到了返回给我的宏扩展?!?
然后我想我可以在调用宏之前使用 alter-meta!
暂时从宏中删除私有(private)元。因此:
(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!
在编译时工作。所以如果有人知道,请回答!
最佳答案
And for my simple macro above it does, and return 10. But on more complicated macros, which need to be expanded further, it doesn't, and instead I get the macro-expansion returned to me ?!?
是的。正如您发现的,当您编写 (defmacro m [x] (list x x))
时,您:
- 定义一个函数
m
,它使用表单作为输入并生成表单作为输出 - 告诉编译器查找诸如
(m a)
之类的调用,并将其替换为在编译时调用m
函数的结果
通过调用 #'m
,您可以绕过第 2 步:没有调用宏 m
,因此编译器不会在编译时调用它,或者用结果替换调用代码。由于 #'m
只是一个常规函数,它将代码作为输入并生成代码,因此当您绕过特殊的编译器行为并在运行时调用它时,您当然会得到代码作为结果(您可以不用做太多事情,因为它已经是运行时了)。
不过,好消息是:无论如何,很少有令人信服的理由将宏设置为私有(private),因为让其他命名空间调用它不会有什么坏处。私有(private)宏所做的全部工作就是扩展为客户端可以手动编写的代码。因此,如果您控制这个宏,您可能应该将其公开。如果不这样做,那么您可以编写宏为您编写的任何代码。
如果你绝对坚持调用别人的私有(private)宏,那么你可以将部分(1)和(2)分开,在某种程度上:定义你自己的宏,其实现委托(delegate)给支持另一个私有(private)变量的函数命名空间:
(defmacro cheat [& args]
(apply #'m &form &env args))
因为cheat
是你自己的宏,所以你可以用通常的方式调用它,利用编译器的“在编译时调用它”机制。然后,您委托(delegate)给生成所需代码的函数,并显式传递 &form
和 &env
。
关于clojure - 从 Clojure 中的另一个命名空间调用私有(private)宏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51871515/