clojure recur和命令循环 [英] clojure recur vs imperative loop
问题描述
学习Clojure并尝试了解实现:
与
有什么区别: (def factorial
(fn [n]
(loop [cnt n acc 1]
(if(zero?cnt)
acc
(* cnt)(* acc cnt))
;在循环中cnt将取值(dec cnt)
; acc将取值(* acc cnt)
) ))
和以下类似C的伪代码
function factorial(n)
for(cnt = n,acc = 1){
if(cnt == 0)return acc;
cnt = cnt-1;
acc = acc * cnt;
}
//在循环cnt中将取值(dec cnt)
//并且acc将取值(* acc cnt)
Clojure的循环和recur是专门用于编写简单命令循环的表单吗?
(假设伪代码的for创建它自己的范围,因此cnt和acc仅存在于循环内)
< em> Clojure的循环
和 recur
是专门用于编写简单的命令循环的表单 p>
是的。
功能性术语:
- 循环是递归的简化形式 tail-recursion 。
- 变量不会在循环体中修改。
Clojure的重复
对尾部递归调用周围的递归点。
- 它重复使用一个堆栈框架,因此工作更快,避免堆栈
溢出。 - 它只能作为任何调用中所做的最后一件事,即所谓的尾位置。
每次连续的 recur
调用都会覆盖最后一个。
递归点是
- a
fn
或或
可能伪装在
li> a loop
表单,它还绑定/设置/初始化
本地/变量。
因此,您的 factorial
函数可以重写
(def factorial
(fn [n]
(fn fact [cnt acc]
zero?cnt)
acc
(fact(dec cnt)(* acc cnt))))
n 1)))
...这是更慢,并有风险堆栈溢出。
不是每个C / C ++循环平滑地翻译。你可以从嵌套循环中得到麻烦,其中内部循环修改外部变量中的变量。
顺便说一句, factorial
函数
- 会很快导致整数溢出。如果你想避免
这个,使用1.0
而不是1
得到浮点数b $ b算术,或使用*'
而不是*
来获得Clojure的BigInt
算术。 - 会在负参数上循环。
后者的快速修正是
(def factorial
(fn [n]
(loop [cnt n acc 1]
(if(pos?cnt)
* acccnt))
acc))))
; 1
...虽然最好是返回 nil
或 Double.NEGATIVE_INFINITY
。
Learning Clojure and trying to understand the implementation:
What's the difference from:
(def factorial
(fn [n]
(loop [cnt n acc 1]
(if (zero? cnt)
acc
(recur (dec cnt) (* acc cnt))
; in loop cnt will take the value (dec cnt)
; and acc will take the value (* acc cnt)
))))
and the following C-like pseudocode
function factorial (n)
for( cnt = n, acc = 1) {
if (cnt==0) return acc;
cnt = cnt-1;
acc = acc*cnt;
}
// in loop cnt will take the value (dec cnt)
// and acc will take the value (* acc cnt)
Are clojure's "loop" and "recur", forms specifically designed to code a simple imperative loop ? (assuming pseudocode's "for" creates it's own scope, so cnt and acc exists only inside the loop)
Are Clojure's loop
and recur
forms specifically designed to code a simple imperative loop?
Yes.
In functional terms:
- A loop is a degenerate form of recursion called tail-recursion.
- The 'variables' are not modified in the body of the loop. Instead, they are re-incarnated whenever the loop is re-entered.
Clojure's recur
makes a tail-recursive call to the surrounding recursion point.
- It re-uses the one stack frame, so working faster and avoiding stack overflow.
- It can only happen as the last thing to do in any call - in so-called tail position.
Instead of being stacked up, each successive recur
call overwrites the last.
A recursion point is
- a
fn
form, possibly disguised indefn
orletfn
OR - a
loop
form, which also binds/sets-up/initialises the locals/variables.
So your factorial
function could be re-written
(def factorial
(fn [n]
((fn fact [cnt acc]
(if (zero? cnt)
acc
(fact (dec cnt) (* acc cnt))))
n 1)))
... which is slower, and risks stack overflow.
Not every C/C++ loop translates smoothly. You can get trouble from nested loops where the inner loop modifies a variable in the outer one.
By the way, your factorial
function
- will cause integer overflow quite quickly. If you want to avoid
this, use
1.0
instead of1
to get floating point (double) arithmetic, or use*'
instead of*
to get Clojure'sBigInt
arithmetic. - will loop endlessly on a negative argument.
A quick fix for the latter is
(def factorial
(fn [n]
(loop [cnt n acc 1]
(if (pos? cnt)
(recur (dec cnt) (* acc cnt))
acc))))
; 1
... though it would be better to return nil
or Double.NEGATIVE_INFINITY
.
这篇关于clojure recur和命令循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!