总结:(>>=) 和 traverse 看起来很相似,因为它们都是函子的箭头映射,而 foldMap (几乎)是专门的 traverse。
在我们开始之前,需要解释一些术语。考虑fmap:
fmap :: Functor f => (a -> b) -> (f a -> f b)
Haskell Functor 是 Hask 类别(以 Haskell 函数为箭头的类别)到其自身的函子。在范畴论术语中,我们说(专门的)fmap 是这个函子的箭头映射,因为它是函子中将箭头指向箭头的部分。 (为了完整起见:函子由箭头映射和对象映射组成。在这种情况下,对象是 Haskell 类型,因此对象映射将类型转换为类型——更具体地说, Functor 的对象映射是它的类型构造函数。)
我们还需要牢记范畴和函子定律:
-- Category laws for Hask:
f . id = id
id . f = f
h . (g . f) = (h . g) . f
-- Functor laws for a Haskell Functor:
fmap id = id
fmap (g . f) = fmap g . fmap f
接下来,我们将使用 Hask 以外的类别,以及不是 Functors 的函子。在这种情况下,我们将id 和(.) 替换为适当的标识和组合,fmap 替换为适当的箭头映射,在一种情况下,= 替换为适当的箭头相等。
(=
从答案中更熟悉的部分开始,对于给定的 monad m,a -> m b 函数(也称为 Kleisli 箭头)形成一个类别(m 的 Kleisli 类别),与 return作为身份和(<=<) 作为组合。三类法则,在这种情况下,就是the monad laws:
f <=< return = return
return <=< f = f
h <=< (g <=< f) = (h <=< g) <=< f
现在,您问的是翻转绑定:
(=<<) :: Monad m => (a -> m b) -> (m a -> m b)
原来(=<<) 是从m 的 Kleisli 范畴到 Hask 的函子的箭头映射。应用于(=<<) 的函子定律相当于两个单子定律:
return =<< x = x -- right unit
(g <=< f) =<< x = g =<< (f =<< x) -- associativity
遍历
接下来,我们需要绕道Traversable(答案末尾提供了本节结果证明的草图)。首先,我们注意到 all 应用函子 f 的 a -> f b 函数一次获取(而不是每次一个,如指定 Kleisli 类别时)形成一个类别,@987654355 @ 作为身份,Compose . fmap g . f 作为组合。为此,我们还必须采用更宽松的箭头等式,它忽略了Identity 和Compose 样板(这只是必要的,因为我是用伪Haskell 编写的,而不是正确的数学符号) .更准确地说,我们将考虑使用Identity 和Compose 同构的任何组合作为相等箭头可以相互转换的任何两个函数(或者,换句话说,我们不会区分a 和Identity a ,也不在f (g a)和Compose f g a之间)。
让我们将该类别称为“可遍历类别”(因为我现在想不出更好的名称)。在具体的 Haskell 术语中,此类别中的箭头是一个函数,它在任何先前现有层的“下方”添加了一个额外的 Applicative 上下文层。现在,考虑traverse:
traverse :: (Traversable t, Applicative f) => (a -> f b) -> (t a -> f (t b))
给定一个可遍历容器的选择,traverse 是函子从“可遍历类别”到自身的箭头映射。它的函子定律相当于可遍历定律。
简而言之,(=<<) 和 traverse 对于涉及 Hask 之外的类别的函子来说都是 fmap 的类似物,因此它们的类型有点相似也就不足为奇了其他。
折叠地图
我们仍然需要解释所有这些与foldMap 有什么关系。答案是foldMap 可以从traverse 恢复(参见danidiaz's answer——它使用traverse_,但由于应用函子是Const m,结果基本相同):
-- cf. Data.Traversable
foldMapDefault :: (Traversable t, Monoid m) => (a -> m) -> (t a -> m)
foldMapDefault f = getConst . traverse (Const . f)
感谢const/getConst 同构,这显然等同于:
foldMapDefault' :: (Traversable t, Monoid m)
=> (a -> Const m b) -> (t a -> Const m (t b))
foldMapDefault' f = traverse f
这只是 traverse 专用于 Monoid m => Const m 应用函子。尽管Traversable 不是Foldable 并且foldMapDefault 不是foldMap,这为foldMap 的类型类似于traverse 的类型以及传递性地类似于(=<<) 的类型提供了一个很好的理由。
作为最后的观察,请注意带有应用函子 Const m 的“可遍历类别”的箭头对于某些 Monoid m 确实不形成子类别,因为没有身份除非Identity 是应用函子的可能选择之一。这可能意味着从这个答案的角度来看,关于foldMap 没有什么值得说的了。提供子类别的应用函子的唯一单一选择是Identity,这并不奇怪,因为在容器上使用Identity 的遍历相当于fmap。
附录
这是推导traverse 结果的粗略草图,从我几个月前的笔记中抽出,并进行了最少的编辑。 ~ 表示“等于(某些相关的)同构”。
-- Identity and composition for the "traversable category".
idT = Identity
g .*. f = Compose . fmap g . f
-- Category laws: right identity
f .*. idT ~ f
f .*. idT
Compose . fmap f . idT
Compose . fmap f . Identity
Compose . Identity . f
f -- using getIdentity . getCompose
-- Category laws: left identity
idT .*. f ~ f
idT .*. f
Compose . fmap Identity . f
f -- using fmap getIdentity . getCompose
-- Category laws: associativity
h .*. (g .*. f) ~ (h .*. g) .*. f
h .*. (g .*. f) -- LHS
h .*. (Compose . fmap g . f)
Compose . fmap h . (Compose . fmap g . f)
Compose . Compose . fmap (fmap h) . fmap g . f
(h .*. g) .*. f -- RHS
(Compose . fmap h . g) .*. f
Compose . fmap (Compose . fmap h . g) . f
Compose . fmap (Compose . fmap h) . fmap g . f
Compose . fmap Compose . fmap (fmap h) . fmap g . f
-- using Compose . Compose . fmap getCompose . getCompose
Compose . Compose . fmap (fmap h) . fmap g . f -- RHS ~ LHS
-- Functor laws for traverse: identity
traverse idT ~ idT
traverse Identity ~ Identity -- i.e. the identity law of Traversable
-- Functor laws for traverse: composition
traverse (g .*. f) ~ traverse g .*. traverse f
traverse (Compose . fmap g . f) ~ Compose . fmap (traverse g) . traverse f
-- i.e. the composition law of Traversable