Foldable 是一个非常令人困惑的类,老实说,因为它没有太多的法律,而且几乎可以为几乎任何给定类型编写很多不同的Foldable 实例。幸运的是,可以基于相同类型的 Traversable 实例(如果有的话)以纯机械方式确定 Foldable 实例应该做什么。
我们有
class (Functor t, Foldable t) => Traversable t where
traverse :: Applicative f => (a -> f b) -> t a -> f (t b)
Traversable 有几个不同的定律,但事实证明最重要的一个是traverse Identity = Identity。让我们看看这如何适用于Maybe:
traverse :: Applicative f => (a -> f b) -> Maybe a -> f (Maybe b)
traverse g Nothing = _none
traverse g (Just a) = _some
现在在第一种情况下,你需要产生f (Maybe b),而你只有g :: a -> f b。由于您没有任何 f 值,并且您没有任何 a 值,因此您唯一可以生成的是 pure Nothing。
在第二种情况下,您必须产生f (Maybe b),并且您有g :: a -> f b 和a。因此,唯一有趣的开始方式是将g 应用于a,得到g a :: f b。现在你有两个选择要考虑:你可以丢弃这个值,只返回Nothing,或者你可以把它包裹在Just中。
根据身份法,traverse Identity (Just a) = Identity (Just a)。所以你不能返回Nothing。 唯一的合法定义是
traverse _ Nothing = pure Nothing
traverse g (Just a) = Just <$> g a
Maybe 的 Traversable 实例完全由 Traversable 定律和参数决定。
现在可以使用traverse 折叠:
foldMapDefault :: (Traversable t, Monoid m)
=> (a -> m) -> t a -> m
foldMapDefault f xs =
getConst (traverse (Const . f) xs)
因为这适用于Maybe,
foldMapDefault f Nothing =
getConst (traverse (Const . f) Nothing)
foldMapDefault f (Just a) =
getConst (traverse (Const . f) (Just a))
扩展我们的定义,
foldMapDefault f Nothing = getConst (pure Nothing)
foldMapDefault f (Just a) = getConst (Just <$> (Const (f a)))
根据pure 和<$> 对Const 的定义,这些是
foldMapDefault f Nothing = getConst (Const mempty)
foldMapDefault f (Just a) = getConst (Const (f a))
解开构造函数,
foldMapDefault f Nothing = mempty
foldMapDefault f (Just a) = f a
这确实是为Maybe 定义foldMap 的方式。