clojure recur vs命令循环

时间:2014-12-28 05:20:12

标签: clojure

学习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只存在于循环中)

2 个答案:

答案 0 :(得分:23)

Clojure的looprecur表单是否专门设计用于编写简单的命令式循环?

是。

在功能方面:

  • 循环是一种简并形式的递归,称为尾递归
  • '变量'不会在循环体中修改。代替, 每当重新进入循环时,它们都会被重新化身。

Clojure的recur对周围的递归点进行尾递归调用

  • 它重新使用一个堆栈帧,因此工作速度更快,避免堆栈 溢出。
  • 它只能作为任何调用中的最后一件事 - 在所谓的尾部位置中。

每次连续recur调用都会覆盖最后一个,而不是堆叠起来。

递归点是

  • fn表格,可能伪装成defnletfn
  • 一个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

...虽然最好返回nilDouble.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是一个更受限制的结构。