Clojure递归宏定义

4

你好,我正在学习Clojure宏,尝试编写一个将中缀表达式转换为前缀表达式的宏,例如:(9 + (1 * 3)) => (+ 9 (* 1 3))。

(defn infix [form]
  (list (second form) (first form) (nth form 2)))

(defmacro r-infix [form]
  (if (coll? form)
    (map r-infix (infix form))
    form))

(r-infix (9 + (1 * 2)));;=>ArityException

但是,如果按照以下方式定义宏,它就可以正常工作:
(defn infix [form]
  (list (second form) (first form) (nth form 2)))

(defn r-infix-fn [form]
  (if (coll? form)
    (map r-infix-fn (infix form))
    form))

(defmacro r-infix [form]
  (r-infix-fn form))

(r-infix (9 + (1 * 2)));;=>11

我在调试第一个示例时遇到了一些困难,有人能帮忙吗?


可能相关:https://groups.google.com/forum/m/#!topic/clojure/lwkFHShlVj8 - Carcigenicate
似乎是一个几乎完全一样的“离线副本”,链接为:https://groups.google.com/forum/m/#!topic/clojure/bL6ORz4G-vQ - Carcigenicate
1个回答

3
在Clojure中,宏不是“一等公民”,这意味着它们不能像数据和函数(不是宏)那样在所有方面使用。
你不能映射一个宏 :-(
因此,第一个示例尝试将宏传递给函数,结果会出现有关无法获取宏值的错误。为了探讨这个问题,让我们与一个足够先进的编译器进行虚拟对话。
我:你好编译器,请使用此宏来转换所有这些表达式。
足够先进的编译器:我试图查看您的宏,并且在我读取它时尝试评估它,我不确定您想要我传递什么给这个函数,当我读取宏时,它消失了?
我:哦,抱歉,我只是想要一个可以接受列表并改变其结构的东西。
足够先进的编译器:那听起来像一个函数 :-)
宏是用特殊标志标记的函数,这会导致它们在读取时运行,并在最终代码准备好运行之前完全完成。当涉及编写编译器时,将它们传递给其他不是宏本身的内容非常困难。
这种情况的影响是一个常见的反模式,称为宏污染,在其中代码仅作为宏编写,因为它需要以某种动态方式调用另一个宏。然后,结果会编写更多的代码作为宏,并且这是一个恶性循环。
您的第二种方法展示了正确的方法,在其中所有宏的工作都在普通函数中完成(恰好由宏调用),这些函数被包装在薄的宏层中作为入口点。当稍后有人需要来应用r-inflix-fn到其他东西时,例如树,则他们不必使自己的新代码成为宏来调用您的代码。
总的来说,对于任何仅通过宏访问的代码都应该持怀疑态度。

1
很好的回答,但它忽略了一些重要的东西:第一个例子并没有引起“宏值”错误;实际上它引起了一个参数异常。你能解释一下吗? - Carcigenicate
2
@Carcigenicate 我认为问题在于第一个例子没有通过变量调用宏,而是通过本地别名调用自身:(defmacro foo [] body) 展开后类似于 (def ^:macro foo (fn foo [&form &env] body)),而被传递给 map 的是“内部”的 foo,也就是由 fn 命名的那个。由于它是本地变量,所以没有被标记为宏,并且它会带有两个额外的“隐藏”参数,因此会出现参数不匹配的情况。 - amalloy
@amalloy 嗯,有趣。我记得在 REPL 中尝试给它三个参数了。不过没关系。 - Carcigenicate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接