我们如何在Clojure中进行左右折叠?

时间:2013-05-28 11:20:40

标签: clojure

减少工作正常,但它更像折叠左。 是否有任何其他形式的减少让我折叠到右边?

2 个答案:

答案 0 :(得分:78)

clojure标准库只有fold-left(reduce)的原因实际上是非常微妙的,这是因为clojure不够懒惰以获得折叠权的主要好处。

像haskell这样的语言折叠权的主要好处是它实际上可以短路。 如果我们foldr (&&) True [False, True, True, True, True]实际得到评估的方式非常有启发性。它唯一需要评估的是具有1个参数的函数and(第一个False)。一旦到达那里它就知道答案,而不需要评估True中的任何一个。

如果仔细观察图片:

enter image description here

你会看到虽然从概念上说右折开始并且列表的结尾向前移动,但实际上它开始在列表的前面进行评估。

这是一个懒惰/ curried函数和尾递归可以带来clojure无法获益的例子。

奖金部分(感兴趣的人)

基于vemv的推荐,我想提一下Clojure为核心命名空间添加了一个新函数来解决这个限制,即Clojure不能有懒惰的右边折叠。核心命名空间中有一个名为reduced的函数,它允许您使Clojure的reduce更加懒惰。它可以用来通过告诉它不要查看列表的其余部分来短路reduce。例如,如果你想要乘以数字列表,但有理由怀疑列表偶尔会包含零,并希望通过在遇到零时不查看列表的其余部分来处理这个特殊情况,那么你可以编写以下multiply-all函数(请注意使用reduced表示最终答案为0,无论列表的其余部分是什么)。

(defn multiply-all [coll]
  (reduce
   (fn [accumulator next-value]
     (if (zero? next-value)
       (reduced 0)
       (* accumulator next-value)))
   1
   coll))

然后为了证明它是短路的,你可以乘以一个无限的数字列表,它恰好包含一个零,并看到它确实以0

的答案终止
(multiply-all
 (cycle [1 2 3 4 0]))

答案 1 :(得分:4)

让我们看看每个的可能定义:

(defn foldl [f val coll]
  (if (empty? coll) val
    (foldl f (f val (first coll)) (rest coll))))

(defn foldr [f val coll]
  (if (empty? coll) val
    (f (foldr f val (rest coll)) (first coll))))

请注意,只有foldl处于尾部位置,递归调用可以由recur替换。因此,对于recurfoldl不会占用堆栈空间,而foldr则会占用堆栈空间。这就是reducefoldl类似的原因。现在让我们试一试:

(foldl + 0 [1 2 3]) ;6
(foldl - 0 [1 2 3]) ;-6
(foldl conj  [] [1 2 3]) ;[1 2 3]
(foldl conj '() [1 2 3]) ;(3 2 1)

(foldr + 0 [1 2 3]) ;6
(foldr - 0 [1 2 3]) ;-6
(foldr conj  [] [1 2 3]) ;[3 2 1]
(foldr conj '() [1 2 3]) ;(1 2 3)

你有什么理由要折叠吗?我认为foldr最常见的用法是将前后列表放在一起。在Clojure中我们不需要它,因为我们可以只使用向量。避免堆栈溢出的另一个选择是使用惰性序列:

(defn make-list [coll]
  (lazy-seq
    (cons (first coll) (rest coll))))

所以,如果你想向右折叠,一些有效的替代品

  1. 改为使用矢量。
  2. 使用懒惰序列。
  3. 使用reduced短路reduce
  4. 如果你真的想潜入兔子洞,请使用传感器。