减少工作正常,但它更像折叠左。 是否有任何其他形式的减少让我折叠到右边?
答案 0 :(得分:78)
clojure标准库只有fold-left(reduce)的原因实际上是非常微妙的,这是因为clojure不够懒惰以获得折叠权的主要好处。
像haskell这样的语言折叠权的主要好处是它实际上可以短路。
如果我们foldr (&&) True [False, True, True, True, True]
实际得到评估的方式非常有启发性。它唯一需要评估的是具有1个参数的函数and
(第一个False
)。一旦到达那里它就知道答案,而不需要评估True
中的任何一个。
如果仔细观察图片:
你会看到虽然从概念上说右折开始并且列表的结尾向前移动,但实际上它开始在列表的前面进行评估。
这是一个懒惰/ 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
替换。因此,对于recur
,foldl
不会占用堆栈空间,而foldr
则会占用堆栈空间。这就是reduce
与foldl
类似的原因。现在让我们试一试:
(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))))
所以,如果你想向右折叠,一些有效的替代品
reduced
短路reduce
。