【问题标题】:When to use pure recursion and when to use loop/recur?何时使用纯递归以及何时使用循环/递归?
【发布时间】:2012-12-25 16:52:06
【问题描述】:

“纯递归”在这里是虚构的,请见谅。

以下是使用两种不同递归方法的两个示例。一个比另一个的使用指南是什么?

(defn take-while
  "Returns a lazy sequence of successive items from coll while
  (pred item) returns true. pred must be free of side-effects."
  {:added "1.0"
   :static true}
  [pred coll]
  (lazy-seq
   (when-let [s (seq coll)]
       (when (pred (first s))
         (cons (first s) (take-while pred (rest s)))))))

(defn take-last
  "Returns a seq of the last n items in coll.  Depending on the type
  of coll may be no better than linear time.  For vectors, see also subvec."
  {:added "1.1"
   :static true}
  [n coll]
  (loop [s (seq coll), lead (seq (drop n coll))]
    (if lead
      (recur (next s) (next lead))
      s)))

【问题讨论】:

  • 我真的不明白你在说什么指导方针。在第一个示例中,您根本无法选择 loop/recur 方式,因为 take-while 未在 tail-position 中使用。问题是什么? 循环/递归 如果可以使用它总是比函数递归调用更好,@mikera 解释了原因。

标签: recursion clojure


【解决方案1】:

需要考虑的几个因素:

  • 循环/递归不消耗堆栈空间 - 因此,如果您要进行深度嵌套递归(否则可能会导致StackOverflowError),这是正确的选择
  • 循环/递归更快 - 它是 Clojure 中最有效的构造之一,正确完成后它应该与 Java 代码中等效 for 循环的速度相匹配
  • 普通递归更惯用 - 平均而言,它倾向于为您提供更清晰、功能更强大的代码,而循环/递归倾向于将您推向命令式迭代风格
  • 循环/递归有更多限制 - 你只能在尾部位置递归,你不能在两个不同的函数之间进行相互递归,等等。有时根本不可能使循环/递归工作,在其他时候,您可能需要扭曲代码才能这样做。

【讨论】:

  • 编译器是否可以将上述 take-while 形式的惯用递归(递归是最后一次)转换为循环/递归形式(或它为循环/重复形式)?
  • @Hendekagon:在这种情况下不是,因为递归不在尾部位置。理论上它可以在其他情况下,但我相信 Rich Hickey 的设计决定不自动执行此操作 - 所以如果您想要尾递归,您必须使用 recur 明确要求
【解决方案2】:

使用lazy-seq/lazy-cons 机制的唯一原因是生成惰性序列。如果您不需要它们,那么毫无疑问应该使用loop/recur

【讨论】:

    【解决方案3】:

    首先在编写函数时使用普通递归。如果可以的话,一旦一切正常,然后将其更改为重复出现。

    TCO 的一个问题是,如果你阻止递归,你会得到无限的外观。如果没有,您的代码会因堆栈溢出而崩溃,这正是您想要的。当我第一次听说它时,我不喜欢 recur 的想法——大多数优化应该只是发生——但是能够将其关闭是很好的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-08-02
      • 1970-01-01
      • 2011-07-16
      • 2014-12-10
      • 2018-09-03
      • 2021-04-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多