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

How can I force evaluation of nested macros in 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 在此上下文中。

你做的事情看起来应该很简单,因为我们习惯于用函数来思考。函数可以很好地组合,您可以轻松地将一个大函数拆分成几个小函数,然后将每个函数的结果组合成一个更大的结果。宏并没有这个 属性:你有点必须制作一个巨大的泥球来同时完成所有事情,将事情分解成更小的宏通常是行不通的。很不幸,你想做的事其实挺难的!

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

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

遗憾的是,我真的没有时间写出我对这两种解决方案的意思的示例片段,部分原因是我很难清楚地了解您提供的所有示例宏应该如何工作一起工作。但希望这堆散文仍然有点用处。

当表单 (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 就足够了,符号解析。