clojure recur vs imperative loop
学习Clojure并尝试了解实现:
与以下内容有何区别:
1 2 3 4 5 6 7 8 9 | (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) )))) |
和以下类似C的伪代码
1 2 3 4 5 6 7 8 | 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) |
clojure的" loop "和" recur "是专门设计用来编写简单命令式循环的形式吗?
(假设伪代码的" for "创建了它自己的作用域,因此cnt和acc仅存在于循环中)
Clojure的
是的。
在功能方面:
- 循环是递归的简并形式,称为尾递归。
-
循环主体中的"变量"未修改。代替,
每当重新进入循环时,它们就会重新被训练。
Clojure \\的
-
它重用了一个堆栈框架,因此工作更快,避免了堆栈
溢出。 - 它只能在任何通话中作为最后的动作发生-在所谓的尾巴位置。
每个连续的
递归点是
-
fn 形式,可能以defn 或letfn 伪装或 -
loop 形式,它也可以绑定/设置/初始化
当地人/变量。
因此您的
1 2 3 4 5 6 7 | (def factorial (fn [n] ((fn fact [cnt acc] (if (zero? cnt) acc (fact (dec cnt) (* acc cnt)))) n 1))) |
...速度较慢,并且有堆栈溢出的风险。
并非每个C / C循环都能顺利转换。您可能会从嵌套循环中麻烦,在嵌套循环中,内部循环会修改外部循环中的变量。
顺便说一下,您的
-
将很快导致整数溢出。如果你想避免
为此,使用1.0 而不是1 来获取浮点数(双精度)
算术运算,或使用*' 而不是* 来获取Clojure \\的BigInt
算术。 - 会否定论点无休止地循环。
后者的快速解决方案是
1 2 3 4 5 6 7 | (def factorial (fn [n] (loop [cnt n acc 1] (if (pos? cnt) (recur (dec cnt) (* acc cnt)) acc)))) ; 1 |
...尽管最好返回
一种查看
要查看其功能,请以您的示例为例
1 2 3 4 5 6 | (def factorial (fn [n] (loop [cnt n acc 1] (if (zero? cnt) acc (recur (dec cnt) (* acc cnt)))))) |
并重写它,以便将
1 2 3 4 5 6 7 8 9 | (def factorial-helper (fn [cnt acc] (if (zero? cnt) acc (recur (dec cnt) (* acc cnt))))) (def factorial' (fn [n] (factorial-helper n 1))) |
现在您可以看到helper函数只是在调用自身;您可以将
1 2 3 4 5 | (def factorial-helper (fn [cnt acc] (if (zero? cnt) acc (factorial-helper (dec cnt) (* acc cnt))))) |
在
我认为一个重要的想法是,它允许基础实现成为命令性循环,但是您的Clojure代码仍然保持功能。换句话说,它不是允许您编写涉及任意分配的命令性循环的构造。但是,如果以这种方式构造功能代码,则可以获得与命令循环相关的性能优势。
一种将命令式循环成功转换为这种形式的方法是将命令式分配更改为递归调用的自变量参数"已分配"的表达式。但是,当然,如果遇到命令式循环进行任意分配,则可能无法将其转换为这种形式。在此视图中,