【问题标题】:clojure recursive macro definitionclojure 递归宏定义
【发布时间】:2017-12-11 16:49:07
【问题描述】:

嗨,我正在学习 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

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

【问题讨论】:

标签: recursion clojure macros


【解决方案1】:

Clojure 中的宏不是“一等公民”,这意味着它们不能以数据和函数(不是宏)可以使用的所有方式使用。

你不能映射一个宏:-(

所以第一个示例尝试将宏传递给函数,这会导致无法获取宏的值的错误。为了探究为什么会这样,让我们​​想象一下与一个足够先进的编译器的对话。

:你好编译器,请用这个宏来转换所有这些表达式。

Sufficiently-Advanced-Compiler:我试图查看你的宏,它试图在我阅读它时进行评估,我不确定你想要什么 我传递给这个函数,当我阅读它时宏就消失了???

:哦,抱歉,我只是想要一些可以获取列表并进行更改的东西。

Sufficiently-Advanced-Compiler:这听起来像一个函数 :-)

宏是用特殊标志标记的函数,该标志使它们在读取时运行,并在最终代码准备好运行之前完全完成。这使得在编写编译器时将它们传递给本身不是宏的其他东西非常混乱。

这样做的效果是一种称为宏传染的常见反模式,其中代码被编写为宏只是因为它需要以某种动态方式调用另一个宏。然后结果更多的代码被写成一个宏,这是一个螺旋式下降。

您的第二种方法显示了执行此操作的正确方法,其中宏的所有工作都在普通函数(恰好由宏调用)中完成,这些函数被包装在作为入口点的薄宏层中。当以后有人需要出现并将 r-inflix-fn 应用于其他东西时,也许是一棵树,那么他们不必将他们的新代码变成一个宏来调用你的。

一般来说,对任何只能通过宏访问的代码持怀疑态度是明智的。

【讨论】:

  • 很好的答案,但它遗漏了一些重要的东西:第一个示例 not 会导致“宏的值”错误;它实际上确实导致了一个arity异常。你能解释一下吗?
  • @Carcigenicate 我认为这是因为第一个示例不是通过其 var 调用宏,而是通过其自身的本地别名:(defmacro foo [] body) 扩展为类似于(def ^:macro foo (fn foo [&form &env] body)),它是“内部”foo,由fn命名的那个,正在传递给map。这没有标记为宏(因为它是本地的),并且它需要两个额外的“隐藏”参数,因此数量不匹配。
  • @amalloy 嗯,很有趣。当我在 REPL 中乱搞时,我以为我尝试给它 3 个 args。哦,好吧。
猜你喜欢
  • 2019-03-31
  • 1970-01-01
  • 2011-03-13
  • 2023-03-13
  • 2012-01-04
  • 1970-01-01
  • 2010-11-21
  • 2017-09-18
  • 2012-10-04
相关资源
最近更新 更多