我们如何在Clojure中左右折叠?
减less工作正常,但更像是折叠。 还有什么其他forms的减less让我折叠到正确的?
clojure标准库只有fold-left(reduce)的原因实际上是非常微妙的,这是因为clojure没有足够的懒惰来获得fold-right的主要好处。
在haskell这样的语言中,折叠权的主要好处是可以实际上短路。 如果我们以foldr (&&) True [False, True, True, True, True]
进行foldr (&&) True [False, True, True, True, True]
,那么这个方法非常有启发性。 它唯一需要评估的是函数and
1个参数(第一个False
)。 一旦它到达那里,它就知道答案,并且不需要评估任何的True
。
如果你仔细看照片:
你会看到,尽pipe从概念上讲,折叠开始和列表的末尾并向前移动,实际上,它开始在列表的前面进行评估。
这是懒惰/咖喱函数和尾recursion可以给clojure不能的好处的一个例子。
奖金部分(对于那些感兴趣的人)
基于vemv的推荐,我想提一下,Clojure在核心命名空间中增加了一个新的function来克服这个限制,即Clojure不能有懒惰的折叠。 在核心命名空间中有一个叫reduce
的函数,可以让你使Clojure的reduce
。 通过告诉它不要查看列表的其余部分,可以用它来缩短reduce
时间。 例如,如果想要乘以数字列表,但有理由怀疑列表偶尔会包含零,并且希望将其作为特殊情况处理,因为一旦遇到零时不查看列表的其余部分,您可以编写下面的multiply-all
函数(注意使用reduced
来表示最终答案是0
而不pipe列表的其余部分是什么)。
(defn multiply-all [coll] (reduce (fn [accumulator next-value] (if (zero? next-value) (reduced 0) (* accumulator next-value))) 1 coll))
然后为了certificate它短路,你可以乘以一个无限的数字列表,它恰好包含一个零,并且看到它确实以0
(multiply-all (cycle [1 2 3 4 0]))
我们来看看每个可能的定义:
(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
处于尾部位置,recursion调用可以由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中,我们不需要这个,因为我们可以使用一个向量。 避免堆栈溢出的另一种select是使用惰性序列:
(defn make-list [coll] (lazy-seq (cons (first coll) (rest coll))))
所以,如果你想正确的select一些有效的select
- 使用一个vector。
- 使用一个懒惰的序列。
- 使用
reduced
到短路reduce
。 - 如果你真的想潜入一个兔子洞,使用一个传感器。