【发布时间】:2013-05-23 21:48:28
【问题描述】:
Reduce 可以正常工作,但它更像是向左折叠。 还有其他形式的 reduce 可以让我向右折叠吗?
【问题讨论】:
标签: clojure
Reduce 可以正常工作,但它更像是向左折叠。 还有其他形式的 reduce 可以让我向右折叠吗?
【问题讨论】:
标签: clojure
clojure 标准库只有 fold-left(reduce)的原因其实很微妙,这是因为 clojure 不够懒惰,无法获得 fold-right 的主要好处。
在像 haskell 这样的语言中,fold-right 的主要好处是它实际上可以短路。
如果我们这样做foldr (&&) True [False, True, True, True, True],它实际被评估的方式是非常有启发性的。它唯一需要评估的是带有 1 个参数的函数 and(第一个 False)。一旦它到达那里,它就知道答案并且不需要评估任何Trues。
如果你仔细看图片:
您会看到,虽然从概念上讲,右折叠开始和列表的末尾并移到前面,但实际上,它从列表的前面开始计算。
这是一个例子,说明惰性/柯里化函数和尾递归可以带来 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]))
【讨论】:
foldr 是尾递归的;如果是这样,那是某种奇怪的与 thunk 相关的尾递归,我还不明白,很想了解。
clojure.core/reduced 函数?
and导致42不被打印,而不是短路的reduce。如果您尝试其他一些示例,您会看到差异。例如(reduce #(and %1 %2) (cons false (repeat true))) 不会终止,但是惰性语言中的等价右折叠将能够避免尝试评估整个序列。
multiply,当 0 是左侧参数时,它不会评估右侧参数,因为在 Clojure 中,参数在传递给函数之前会被评估。这就是为什么if、and、or、if-not 等不是函数的原因。在 Haskell 中,所有这些东西都可以是常规函数。
让我们看一下每个可能的定义:
(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。【讨论】: