我们从foldMap的类型开始:
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
foldMap 的工作原理是将 a -> m 函数映射到数据结构上,然后通过它运行将元素粉碎成单个累积值与 mappend。
接下来,我们注意到,给定某个类型b,b -> b 函数形成一个幺半群,(.) 作为其二元运算(即mappend)和id 作为标识元素(即@ 987654332@。如果你还没有遇到,id 定义为id x = x)。如果我们将 foldMap 专门化为那个幺半群,我们会得到以下类型:
foldEndo :: Foldable t => (a -> (b -> b)) -> t a -> (b -> b)
(我将函数称为foldEndo,因为内函数是从一种类型到相同类型的函数。)
现在,如果我们查看列表的签名foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
我们可以看到 foldEndo 匹配它,除了泛化到任何 Foldable 和一些参数的重新排序。
在我们开始实现之前,有一个技术难题是b -> b 不能直接成为Monoid 的实例。为了解决这个问题,我们使用来自Data.Monoid 的Endo newtype 包装器:
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
这是默认定义you can find in Data.Foldable。最棘手的可能是Endo . f;如果您对此有疑问,请不要将f 视为二元运算符,而是将其视为a -> (b -> b) 类型的一个参数的函数;然后我们用 Endo 包装生成的 endofunction。
至于foldl,推导本质上是相同的,只是我们使用不同的内函数幺半群,flip (.)作为二元运算(即我们以相反的方向组合函数)。