学习Clojure并尝试理解实施:
有什么区别:
(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的伪代码
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的“循环”和“复发”是否专门设计用于编写简单的命令循环? (假设伪代码的“for”创建了它自己的范围,所以cnt和acc只存在于循环中)
答案 0 :(得分:23)
Clojure的loop
和recur
表单是否专门设计用于编写简单的命令式循环?
是。
在功能方面:
Clojure的recur
对周围的递归点进行尾递归调用。
每次连续recur
调用都会覆盖最后一个,而不是堆叠起来。
递归点是
fn
表格,可能伪装成defn
或letfn
或loop
形式,它也绑定/设置/初始化
当地人/变量。 因此可以重写factorial
函数
(def factorial
(fn [n]
((fn fact [cnt acc]
(if (zero? cnt)
acc
(fact (dec cnt) (* acc cnt))))
n 1)))
...速度较慢,存在堆栈溢出的风险。
并非每个C / C ++循环都能平滑转换。您可以从嵌套循环中遇到麻烦,其中内部循环修改外部循环中的变量。
顺便提一下,您的factorial
功能
1.0
而不是1
来获得浮点(双精度)
算术,或使用*'
代替*
来获得Clojure BigInt
算术。后者的快速解决方法是
(def factorial
(fn [n]
(loop [cnt n acc 1]
(if (pos? cnt)
(recur (dec cnt) (* acc cnt))
acc))))
; 1
...虽然最好返回nil
或Double.NEGATIVE_INFINITY
。
答案 1 :(得分:7)
查看loop
/ recur
的一种方法是,它允许您编写功能的代码,但底层实现最终基本上是一个命令式循环。
要了解它是否有效,请举例
(def factorial
(fn [n]
(loop [cnt n acc 1]
(if (zero? cnt)
acc
(recur (dec cnt) (* acc cnt))))))
并重写它,以便将loop
表单分解为单独的辅助函数:
(def factorial-helper
(fn [cnt acc]
(if (zero? cnt)
acc
(recur (dec cnt) (* acc cnt)))))
(def factorial'
(fn [n]
(factorial-helper n 1)))
现在您可以看到辅助函数只是调用自身;您可以使用函数名称替换recur
:
(def factorial-helper
(fn [cnt acc]
(if (zero? cnt)
acc
(factorial-helper (dec cnt) (* acc cnt)))))
在recur
中使用时,您可以查看factorial-helper
,只需进行递归调用,该调用由底层实现进行优化。
我认为一个重要的想法是,它允许底层实现成为命令式循环,但您的Clojure代码仍然保持功能。换句话说,它不是一个允许你编写涉及任意赋值的命令循环的结构。但是,如果以这种方式构建功能代码,则可以获得与命令循环相关的性能优势。
将命令式循环成功转换为此形式的一种方法是将命令式赋值更改为分配给"的表达式。递归调用的参数参数。但是,当然,如果遇到一个执行任意赋值的命令循环,您可能无法将其转换为此形式。在此视图中,loop
/ recur
是一个更受限制的结构。