重新出现在正确的尾部位置
recur in the rightful tail poistion
recur 应该在尾部位置调用,我假设它有效地充当非递归准循环。
是expr-1 or 2被认为在正确的尾部位置但是expr-3的none直到8在以下模拟块结构?否则,如何推理和识别才能进行试错?
(defn foo [x]
(if cond-expr-1
(recur expr-1)
(recur expr-2)))
(defn bar [x]
(if cond-expr-2
(fn-1 (recur expr-3))
(fn-2 (recur expr-4))))
(defn baz [x]
(if cond-expr-3
(if cond-expr-4
(recur expr-5)
(recur expr-6))
(if cond-expr-5
(recur expr-7)
(recur expr-8))))
在expr-3
和expr-4
的情况下,它是一个函数的参数,所以它不在尾部位置。
调用 recur
基本上类似于 goto
或 return
语句,两者都不能在函数参数列表中使用。
因为 if
表达式不是一个函数(它是一个 "special form"),所以没有函数参数列表的问题。
这里是 loop/recur
和使用 while
的更命令式方法之间的比较:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn fib-recur
[arg]
(assert (and (int? arg) (pos? arg)))
; initialize state vars
(loop [N arg
result 1]
(if (zero? N)
result
; compute next state vars
(let [N-next (dec N)
result-next (* result N)]
(recur N-next result-next))))) ; jump to top of loop with next state vars
(defn fib-while
[arg]
(assert (and (int? arg) (pos? arg)))
; initialize state vars
(let [state (atom {:N arg
:result 1})]
(while (pos? (:N @state)) ; must use newest state value for N in test
; compute next state vars
(let [N (:N @state)
result (:result @state)
state-next {:N (dec N)
:result (* result N)}]
(reset! state state-next))) ; save new state & jump to top of loop
(:result @state)))
(dotest
(is= 120 (fib-recur 5))
(is= 120 (fib-while 5)))
如果在表达式后面的当前函数中没有其他要计算的内容,则该表达式位于尾部位置。在您的情况下,foo
和 baz
中的所有 recur
调用都处于尾部位置,因此这两个函数都可以正常编译。
然而,在 bar
中,recur
调用均不被允许,因为 expr-3
或 expr-4
均不在尾部位置。有些函数调用使用每个 recur
调用的结果作为参数——也就是说,函数调用逻辑上跟在 recur
调用之后,因此 recur
不在尾部位置。
这并不是说你不能写 bar
来递归调用它自己,但你必须明确地编码它,如:
(defn bar [x]
(if cond-expr-2
(fn-1 (bar expr-3))
(fn-2 (bar expr-4))))
这是绝对允许的,但是 bar
的这些递归调用将消耗堆栈 space 这意味着如果函数递归调用自身足够多次,您将 运行 出堆栈space。 recur
(和一般的尾递归)很有价值,因为它不涉及进行传统意义上的函数调用——相反,(这里从逻辑上讲)堆栈上的函数参数被新参数和代码跳回到函数的开头,因此没有使用堆栈 space。当然,这意味着第一次调用函数时的原始参数丢失了。
其他版本的 Lisp 不使用 recur
关键字。当这些版本的 Lisp 发现函数正在递归调用自身时,它们会做出与 Clojure 相同的 "tail position" 判断,如果它们发现调用处于尾部位置,它们会执行与 Clojure 相同的 "replace-the-argument-and-jump" 逻辑,而如果他们发现调用不在尾部位置,他们会发出代码来执行 "real" 递归调用,而不是使编译失败。 Clojure 的优势在于,如果调用将被编译为尾递归调用(分支),开发人员可以一目了然。
recur 应该在尾部位置调用,我假设它有效地充当非递归准循环。
是expr-1 or 2被认为在正确的尾部位置但是expr-3的none直到8在以下模拟块结构?否则,如何推理和识别才能进行试错?
(defn foo [x]
(if cond-expr-1
(recur expr-1)
(recur expr-2)))
(defn bar [x]
(if cond-expr-2
(fn-1 (recur expr-3))
(fn-2 (recur expr-4))))
(defn baz [x]
(if cond-expr-3
(if cond-expr-4
(recur expr-5)
(recur expr-6))
(if cond-expr-5
(recur expr-7)
(recur expr-8))))
在expr-3
和expr-4
的情况下,它是一个函数的参数,所以它不在尾部位置。
调用 recur
基本上类似于 goto
或 return
语句,两者都不能在函数参数列表中使用。
因为 if
表达式不是一个函数(它是一个 "special form"),所以没有函数参数列表的问题。
这里是 loop/recur
和使用 while
的更命令式方法之间的比较:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn fib-recur
[arg]
(assert (and (int? arg) (pos? arg)))
; initialize state vars
(loop [N arg
result 1]
(if (zero? N)
result
; compute next state vars
(let [N-next (dec N)
result-next (* result N)]
(recur N-next result-next))))) ; jump to top of loop with next state vars
(defn fib-while
[arg]
(assert (and (int? arg) (pos? arg)))
; initialize state vars
(let [state (atom {:N arg
:result 1})]
(while (pos? (:N @state)) ; must use newest state value for N in test
; compute next state vars
(let [N (:N @state)
result (:result @state)
state-next {:N (dec N)
:result (* result N)}]
(reset! state state-next))) ; save new state & jump to top of loop
(:result @state)))
(dotest
(is= 120 (fib-recur 5))
(is= 120 (fib-while 5)))
如果在表达式后面的当前函数中没有其他要计算的内容,则该表达式位于尾部位置。在您的情况下,foo
和 baz
中的所有 recur
调用都处于尾部位置,因此这两个函数都可以正常编译。
在 bar
中,recur
调用均不被允许,因为 expr-3
或 expr-4
均不在尾部位置。有些函数调用使用每个 recur
调用的结果作为参数——也就是说,函数调用逻辑上跟在 recur
调用之后,因此 recur
不在尾部位置。
这并不是说你不能写 bar
来递归调用它自己,但你必须明确地编码它,如:
(defn bar [x]
(if cond-expr-2
(fn-1 (bar expr-3))
(fn-2 (bar expr-4))))
这是绝对允许的,但是 bar
的这些递归调用将消耗堆栈 space 这意味着如果函数递归调用自身足够多次,您将 运行 出堆栈space。 recur
(和一般的尾递归)很有价值,因为它不涉及进行传统意义上的函数调用——相反,(这里从逻辑上讲)堆栈上的函数参数被新参数和代码跳回到函数的开头,因此没有使用堆栈 space。当然,这意味着第一次调用函数时的原始参数丢失了。
其他版本的 Lisp 不使用 recur
关键字。当这些版本的 Lisp 发现函数正在递归调用自身时,它们会做出与 Clojure 相同的 "tail position" 判断,如果它们发现调用处于尾部位置,它们会执行与 Clojure 相同的 "replace-the-argument-and-jump" 逻辑,而如果他们发现调用不在尾部位置,他们会发出代码来执行 "real" 递归调用,而不是使编译失败。 Clojure 的优势在于,如果调用将被编译为尾递归调用(分支),开发人员可以一目了然。