【问题标题】:clojure recur vs imperative loopclojure recur vs 命令循环
【发布时间】:2014-12-28 05:20:12
【问题描述】:

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

【问题讨论】:

    标签: clojure


    【解决方案1】:

    Clojure 的 looprecur 表单是专门为编写简单的命令式循环而设计的吗?

    是的。

    在功能方面:

    • 循环是一种退化的递归形式,称为 tail-recursion
    • 循环体中的“变量”未修改。反而, 每当重新进入循环时,它们就会重新化身。

    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 会更好。

    【讨论】:

    • 很好的答案,谢谢。只是一个评论:按照奥卡姆剃刀的精神,我将“带有尾调用优化的递归”视为“循环”的一种退化形式:“递归”比“循环”更复杂
    • @LucioM.Tato 正如Mike Fikes 强调的那样,是任意赋值,而不是控制结构,使得循环比尾递归更强大,就像goto 比@ 更强大一样987654342@.
    【解决方案2】:

    查看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)))))
    

    factorial-helper 中使用recur 时,您可以将其视为简单地进行递归调用,该调用已通过底层实现进行了优化。

    我认为一个重要的想法是它允许底层实现成为命令式循环,但您的 Clojure 代码仍然可以正常工作。换句话说,它不是一个允许您编写涉及任意赋值的命令式循环的构造。但是,如果您以这种方式构建函数式代码,您可以获得与命令式循环相关的性能优势。

    将命令式循环成功转换为这种形式的一种方法是将命令式赋值更改为“分配给”递归调用的参数参数的表达式。但是,当然,如果您遇到进行任意赋值的命令式循环,您可能无法将其翻译成这种形式。在这个观点中,loop/recur 是一个更受约束的结构。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-04
      • 2011-04-09
      • 1970-01-01
      • 2012-03-01
      • 1970-01-01
      • 2010-11-06
      • 2012-10-02
      • 2017-03-09
      相关资源
      最近更新 更多