如何在 Clojurescript 中强制评估嵌套宏?



我有一个CLJC文件,它在Clojure中产生以下所需的输出:

(ns myproj.macros-ns)
(defmacro inner-macro [s]
`['(my-ns/my-fn) :from ['~s :all]])
(defmacro outer-macro [y xs]
`(into ['~y '~'<-] (eval '~xs)))
(defmacro macro-context [other-macros]
(let [symbols (eval other-macros)
_ (println "Expanded in macro context" symbols)]
{:result `(list '~symbols '~'+ '~'further-rearrangement)}))

(macro-context (outer-macro ?sym-a (inner-macro ?sym-b)))
Expanded in macro context [?sym-a <- (my-ns/my-fn) :from [?sym-b :all]]
=> {:result ([?sym-a <- (my-ns/my-fn) :from [?sym-b :all]] + further-rearrangement)}

我的问题是:如何在Clojurescript中获得相同的结果?

我的 CLJS 文件如下所示:

(ns myproj.app-ns
(:require-macros [myproj.macros-ns :refer [outer-macro
inner-macro
macro-context]]))
(enable-console-print!)
(macro-context (outer-macro ?sym-a (inner-macro ?sym-b)))

我得到的错误是:

clojure.lang.ExceptionInfo: java.lang.RuntimeException: Unable to 
resolve symbol: outer-macro in this context, compiling:
(/private/var/folders/2g/sfp74ftj6_q1vw51ytjbgvph0000gn/T/form-
init4244939051951953637.clj:13:3) at line 12 
test/macros/cljs/myproj/app_ns.cljs

我最终想做什么,为什么?

我正在编写一个包装 https://github.com/cerner/clara-rules 的框架。Clara 有自己的宏defrule,它使用以下 DSL 语法

(defrule my-rule
[?fact <- (my-ns/my-fn) :from [:all (= ?e (:e this))]
...

我有一个宏,可以将以下内容扩展到上一个:

(macro-context my-rule
[?fact <- (my-ns/my-fn) :from [?e :all]]
...

执行此操作的宏基本上是上面更一般示例中的macro-context。当我只像这样解析语法时,我不调用evalmacroexpand。我能够将所有内容视为符号,将其重写为 Clara 的 DSL,然后将其传递给defrule

我认为这是分解的地方:

(macro-context
[(outer-macro ?fact (inner-macro ?e))]
...

macro-context宏中,outer-macroinner-macro未被评估,此时我需要扩展它们。通过调用 eval,我可以在 Clojure 中得到它,但由于某种原因,在编译 Clojurescript 时,我收到"无法解析在这种情况下的符号outer-macro

当表单(outer-macro ?sym-a (inner-macro ?sym-b))传递到macro-context时,outer-macroinner-macro的(ClojureScript):refer不会影响Clojure宏扩展。特别是,macro-context中使用的eval将无法解析这些符号。

但是,如果您限定这些符号,例如,用

(macro-context (myproj.macros-ns/outer-macro ?sym-a (myproj.macros-ns/inner-macro ?sym-b)))

然后事情就会好起来。

更新

如果在宏定义中添加refer,则可以在 Clojure 中实现所需的引用,如下所示:

(defmacro macro-context [other-macros]
(refer 'myproj.macros-ns :only '[inner-macro outer-macro])
(let [symbols (eval other-macros)
_ (println "Expanded in macro context" symbols)]
{:result `(list '~symbols '~'+ '~'further-rearrangement)}))

有了这个,inner-macroouter-macro将在Clojure*ns*中引用,以镜像您正在扩展的ClojureScript ns。那么在 ClojureScript 中引用macro-context就足够了,符号解析了。

您正在尝试做的事情看起来应该非常简单易行,因为我们习惯于从函数的角度思考。函数是很好的可组合的,您可以轻松地将一个大函数拆分为几个小函数,然后将每个函数的结果组合成一个较大的结果。宏实际上没有这个属性:你必须制作一个巨大的泥球才能一次完成所有事情,而将事情分解成更小的宏通常不起作用。所以不开心,你想做的事情其实是相当困难的!

可以这么说,最直接的方法是屈服于泥球,因为克拉拉已经规定你必须在宏观土地上做所有有趣的逻辑。你可以声明你的最顶层宏必须提前知道问题被分解的所有方式,接受指示应该进行哪些转换的论点,然后自己进行这些转换,而不是将该任务委托给另一个宏(因为正如我们所说,你不能分解宏)。每当您需要引入一种新的灵活性时,编辑该"主"宏以向其添加另一个选项(将它们作为"选项"映射而不是位置参数可能是个好主意)。有点糟糕,但有时这就是生活。

一种初始成本更高的更易于维护的方法是尝试让自己回到函数领域:如上所述编写一个"主宏",但将其分解为函数而不是宏。毕竟,宏只是一个接收源代码并返回源代码的函数,其主要区别在于它是就地扩展的。您可以编写一个行为相同但未就地扩展的函数,并让宏实现在扩展自身时调用它。

可悲的是,我真的没有时间写出我对这两种解决方案的意思的示例片段,部分原因是很难在脑海中保持您提供的所有示例宏应该如何协同工作。但希望这堆散文证明是有用的。

最新更新