clojure - 从 Clojure 中的另一个命名空间调用私有(private)宏?

标签 clojure

在一个文件中给出以下内容:

(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))

如您所见,接下来发生的事情是:

  1. private-macro fn 使用 defn 声明,并设置为 private。
  2. 此函数需要 3 个参数[&form &env a]这就是为什么我们在使用#'调用宏时会得到错误的参数数量(1)异常。
  3. 通过调用 setMacro 方法将 private-macro 变量设置为宏。
  4. 返回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)) 时,您:

  1. 定义一个函数 m,它使用表单作为输入并生成表单作为输出
  2. 告诉编译器查找诸如 (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/

相关文章:

clojure 部分说明

clojure - 如何在 clojure 中生成随机字母数字字符串?

session - 关闭 Sente session 并重定向到 clojure/clojurescript 中的登录页面

clojure - LightTable IDE 传统 REPL 模式?

Clojure:减少三个参数

clojure - 运行 'lein uberjar'制成的jar时如何设置类路径?

clojure - 关闭 Light Table 中的自动完成功能

clojure - 如何映射要执行的 Java 方法的顺序

web - 在 Clojure 中构建 Web Dashboard 是否可行?

clojure - 如何访问向量内的向量中的所有第二个元素?