【问题标题】:Clojure - how macroexpansion works inside of the "some" functionClojure - 宏扩展如何在“some”函数内部工作
【发布时间】:2019-11-07 20:37:08
【问题描述】:

就在我认为自己对宏有相当好的处理能力时,我偶然发现了some 的源代码,乍一看我觉得有点奇怪。

(defn some
  [pred coll]
    (when (seq coll)
      (or (pred (first coll)) (recur pred (next coll)))))

我的第一直觉是这似乎会消耗堆栈,但后来我记得:“不,笨蛋,or 是一个宏,所以它会简单地扩展成大量嵌套的ifs”。

然而,我仔细考虑了一下,最终认为自己在一个角落里。在扩展时,函数源如下所示:

(defn some
  [pred coll]
    (when (seq coll)
      (let [or__4469__auto__  (pred (first coll))]
         (if or__4469__auto__ 
             or__4469__auto__ 
             (recur pred (next coll))))))

现在让我感到困惑的是最后的recur 电话。我一直认为宏扩展发生在运行时之前,但是在这里您必须在运行时实际调用已经扩展的代码才能进行第二个宏扩展....等一下,我想我刚刚弄明白了。

没有第二个宏扩展,没有嵌套的if 块,只有一个if 块。对recur 的调用只是不断重新绑定predcoll,但上面的同一个块一直在测试真相,直到找到它,或者集合用完并返回nil

有人可以确认这是否是正确的解释吗?我最初感到困惑,认为宏扩展和运行时会有交错,其中在运行时对 recur 的调用会以某种方式导致新的宏调用,这没有意义,因为宏扩展必须在运行时之前发生。现在我想我明白了我的困惑在哪里,只有一个宏扩展,并且生成的代码在循环中一遍又一遍地使用。

【问题讨论】:

    标签: clojure macros lisp


    【解决方案1】:

    首先,请注意任何函数都可以用作隐式loop 表达式。此外,recur 就像递归函数调用一样工作,只是它不会因为编译器技巧而耗尽堆栈(这就是为什么 looprecur 是“特殊形式” - 它们不遵守规则正常功能)。

    另外,请记住 when 是一个扩展为 if 表达式的宏。

    说了这么多,你确实得出了正确的结论。

    【讨论】:

      【解决方案2】:

      这里有两种递归模式:

      • or 是隐式递归的,由参数序列引发 表单生成if 表单树。
      • some 函数 是显式递归的,被激怒告诉单 其最后论证的顺序。这个递归的事实是 recurable 无关紧要。

      or 宏除第一个参数之外的每个参数都会生成一个嵌套的 if 表单。例如,...

      => (clojure.walk/macroexpand-all '(or a b c)) 
      (let* [or__5501__auto__ a]
        (if or__5501__auto__ or__5501__auto__
          (let* [or__5501__auto__ b]
            (if or__5501__auto__ or__5501__auto__ c))))
      

      or 有两个参数,所以一个 if 表单。正如Alan Thompson's excellent answer 指出的那样,周围的when 展开为另一个if 形式。

      您可以拥有任意数量的嵌套if 表单,if 树的叶子都位于尾部位置。因此,所有立即递归调用都是recurable。如果没有这样的尾递归,recur 调用将无法编译。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-09
        • 1970-01-01
        相关资源
        最近更新 更多