当我以为我对宏有很好的处理时,我偶然发现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
的调用仅保持重新绑定pred
和coll
的作用,但是上面的同一块继续测试真相,直到找到真相,或者集合用完并返回nil
有人可以确认这是正确的解释吗?最初,我曾使自己感到困惑,认为宏扩展和运行时会交织在一起,其中在运行时对recur的调用将以某种方式导致新的宏调用,这没有意义,因为宏扩展必须在运行时之前进行。现在,我想我看到了我的困惑所在,那里只有一个宏扩展,并且在循环中一遍又一遍地使用生成的代码。
答案 0 :(得分:3)
首先,请注意任何函数都可以用作隐式loop
表达式。此外,recur
的工作方式类似于递归函数调用,只是由于编译器的技巧而不会耗尽堆栈(这就是loop
和recur
是“特殊形式”的原因-它们不遵循正常功能的规则。
另外,请记住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
调用将无法编译。