没有折叠的折叠有什么特性?

时间:2014-01-06 01:47:51

标签: function haskell functional-programming fold

对于FP和Haskell来说,Foldl和folr是2个非常重要的函数,但是我从来没有听过很多关于unsided fold的内容:

fold f [a,b,c,d] = (f (f a b) (f c d))

即,对二元关联函数进行操作的折叠(因此应用顺序无关紧要)。如果我没记错的话,这在数据库中很常见,因为它可以并行化。所以,关于它,我问:

  1. 它和foldr一样,是通用的吗?
  2. 与foldr一样,您可以使用它定义每个重要的功能吗?
  3. 是否有融合规则,类似于foldr / build和unfoldr / destroy?
  4. 为什么几乎没有提到?
  5. 值得一提的任何考虑因素?

2 个答案:

答案 0 :(得分:5)

这通常被认为是树木减少,并且在并行计算中很重要,因为它体现了分而治之的减少。

首先,如果组合函数是非关联的,那么foldlfoldr和“未组合折叠”之间显然存在很大差异,所以让我们假设我们结合了一个关联操作。立即,所有折叠都可以用Monoid表示。

foldlm :: Monoid m => [m] -> m
foldlm = foldl mappend mempty

foldrm :: Monoid m => [m] -> m
foldrm = foldr mappend mempty

usfoldm :: Monoid m => [m] -> m
usfoldm = foldTree mappend mempty . buildTree

foldMap :: Monoid m => (a -> m) -> [a] -> m更好地代表了哪个,foldr默认使用foldMap f = foldr (mappend . f) mempty 定义。

Monoid

如果给出最终提取步骤,只要在树状序列类型上定义Monoid来控制元素 - data Tree a singleton :: a -> Tree a instance Monoid (Tree a) where ... foldTree :: Monoid a => Tree a -> a foldTree . foldMap singleton :: Monoid a => [a] -> a 的组合方式,这就足以产生树状无边折叠

foldMap

最后,我们已经看到我们可以从foldr获得foldr,但我们也可以从foldMap获得newtype Endo a = Endo { appEndo :: a -> a } instance Monoid (Endo a) where mempty = id mappend (Endo f) (Endo g) = Endo (f . g) foldr f z as = appEndo (foldMap (Endo . f) as) z

foldMap

通常,Monoid被认为更原始,因为它让底层foldMap选择其首选折叠方法。这意味着我们可以在每个数据类型级别上自由编写更高效或更平行的折叠,但这样做仍然可能具有挑战性。

值得注意的是,Foldable抽象通常被发现为Foldable的实例方法,这是一种非常流行但更新的Haskell类型类。尽管它具有实用性,但它也被认为有点愚蠢,因为toList :: Foldable f => f a -> [a] 除了那个之外几乎没有什么有意义的法则

Monoid

存在,这也让我们看到foldMap的{​​{1}}性质,因为[a]是我们可以使用Monoid恢复的通用foldr。< / p>

为了进一步研究融合规则,阅读Gershom Bazerman的Building up to a Point via Adjunctions中提出的双类型Buildable是很有价值的。

最后,至于流行度,我认为这绝对是这些天实例化Foldable的首选方法,因为它可以在必要时允许更高效的Monoid折叠,但它肯定比{{1}更新和foldl这可能会导致其相对模糊。

答案 1 :(得分:3)

您正在寻找的功能是Data.Foldable.foldMap

foldMap :: Data.Monoid.Monoid m => (a -> m) -> t a -> m

此函数与foldr同构。作为证据,请注意foldMapfoldr中的一个是Foldable的最小完整定义,这意味着每个都可以用另一个来编写。这肯定地回答了你的前两个问题。

我没有具体了解foldMap的融合规则,但我确信可以存在。foldl。至少,foldr融合规则应该在某种程度上适用。

我不知道为什么勉强提到它。

值得一提的一个考虑因素是,就列表而言,您无法始终充分利用此折叠。由于列表是由cons单元格构成的,所以进行树折叠意味着遍历列表的一半,然后向下递减每一半并再次遍历一半,等等。与foldr或{{1相比,这是一些额外的遍历}}。对于非列表结构,树折可以更多更高效,甚至对于列表,它可以利用这一点。最近有一个很好的blog关于这样的任务。