我们从 的类型开始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
. 为了解决这个问题,我们使用Endo
newtype 包装器来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 (.)
作为二元运算(即我们以相反的方向组合函数)。