Clojure-宏扩展在“某些”函数内部的工作方式

时间:2019-06-26 00:32:28

标签: clojure macros lisp

当我以为我对宏有很好的处理时,我偶然发现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的调用将以某种方式导致新的宏调用,这没有意义,因为宏扩展必须在运行时之前进行。现在,我想我看到了我的困惑所在,那里只有一个宏扩展,并且在循环中一遍又一遍地使用生成的代码。

2 个答案:

答案 0 :(得分:3)

首先,请注意任何函数都可以用作隐式loop表达式。此外,recur的工作方式类似于递归函数调用,只是由于编译器的技巧而不会耗尽堆栈(这就是looprecur是“特殊形式”的原因-它们不遵循正常功能的规则。

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

话虽如此,您的确得出了正确的结论。

答案 1 :(得分:0)

这里有两种递归模式:

  • or 是隐式递归的,由参数序列引起 表单生成if表单树。
  • some 功能是显式递归的,被激发为告诉单个 其最终参数的顺序。这个递归是 recur无关紧要。

第一个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树的叶子都位于尾部。因此,所有recur都可以进行立即递归调用。如果没有这种尾部递归,则recur调用将无法编译。