我们从 的类型开始foldMap:
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
foldMap通过将a -> m函数映射到数据结构上,然后通过它运行将元素粉碎成单个累积值mappend。
接下来,我们注意到,给定某种类型b,b -> b函数形成一个幺半群,(.)作为它的二元运算(即mappend)和id作为恒等元(即mempty。如果您还没有遇到它,id则定义为id x = x)。如果我们专门foldMap研究那个幺半群,我们会得到以下类型:
foldEndo :: Foldable t => (a -> (b -> b)) -> t a -> (b -> b)
(我之所以调用该函数foldEndo,是因为 endofunction 是从一种类型到相同类型的函数。)
现在,如果我们看一下列表的签名foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
我们可以看到它foldEndo匹配它,除了泛化到 anyFoldable和一些参数的重新排序。
在我们开始实现之前,有一个技术难题是b -> b不能直接将Monoid. 为了解决这个问题,我们使用Endonewtype 包装器来Data.Monoid代替:
newtype Endo a = Endo { appEndo :: a -> a }
instance Monoid (Endo a) where
mempty = Endo id
Endo f `mappend` Endo g = Endo (f . g)
写成Endo,foldEndo只是专门的foldMap:
foldEndo :: Foldable t => (a -> Endo b) -> t a -> Endo b
所以我们将直接跳转到foldr,并根据 来定义它foldMap。
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b
foldr f z t = appEndo (foldMap (Endo . f) t) z
这是您可以在 中找到Data.Foldable的默认定义。最棘手的可能是Endo . f;如果您对此有疑问,请f不要将其视为二元运算符,而应将其视为具有 type 的一个参数的函数a -> (b -> b);然后我们用Endo.
至于foldl,推导本质上是相同的,除了我们使用不同的内函数幺半群,与flip (.)作为二元运算(即我们以相反的方向组合函数)。