考虑以下签名foldMap
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
这与“绑定”非常相似,只是交换了参数:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
因此,在我看来,和之间必须存在某种关系Foldable
,但我在超类中找不到它。大概我可以将其中的一个或两个转换为另一个,但我不确定如何。Monoid
Monad
可以详细说明一下关系吗?
考虑以下签名foldMap
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
这与“绑定”非常相似,只是交换了参数:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
因此,在我看来,和之间必须存在某种关系Foldable
,但我在超类中找不到它。大概我可以将其中的一个或两个转换为另一个,但我不确定如何。Monoid
Monad
可以详细说明一下关系吗?
Monoid
和Monad
哇,这实际上是我们可以使用报价的罕见情况之一:
单子只是内函子类别中的一个幺半群,[...]
让我们从一个幺半群开始。集合类别Set
中的幺半群是一组元素m
,其中包含一个空元素mempty
和一个用于组合元素的关联函数mappend
,使得
mempty `mappend` x == x -- for any x
x `mappend` mempty == x -- for any x
-- and
a `mappend` (b `mappend` c) == (a `mappend` b) `mappend` c -- for all a, b, c
请注意,幺半群不限于集合,在Cat
类别(monads)等类别中也存在幺半群。基本上任何时候你都有一个关联的二进制操作和它的标识。
现在一个 monad,它是一个“内函子类别中的monoid”,具有以下属性:
它是一个内函子,这意味着它在Haskell 类型* -> *
的类别中具有类型。Hask
现在,为了更进一步,您必须了解一点范畴论,我将在这里尝试解释:给定两个函子F
和G
,存在从F
到的自然转换,G
当且仅当存在一个函数α
使得每个F a
都可以映射到 a G a
。α
可以是多对一的,但它必须映射F a
. 粗略地说,自然变换是函子之间的函数。
现在在范畴论中,两个范畴之间可以有很多函子。在简化的视图中,可以说我们甚至不关心哪些函子从哪里映射到哪里,我们只关心它们之间的自然转换。
回到 monad,我们现在可以看到“内函子范畴中的monoid”必须具有两个自然变换。让我们调用我们的 monad endofunctor M
:
从恒等式(endo)函子到 monad 的自然转换:
η :: 1 -> M -- this is return
以及从两个单子组合的自然转换并产生第三个单子:
μ :: M × M -> M
既然×
是函子的组成,我们(粗略地说)也可以这样写:
μ :: m a × m a -> m a
μ :: (m × m) a -> m a
μ :: m (m a) -> m a -- join in Haskell
满足这些法律:
μ . M μ == μ . μ M
μ . M η == μ . η M
因此,单子是内函子类别中幺半群的特例。你不能在普通的 Haskell 中为 monad 写一个 monoid 实例,因为 Haskell 的组合概念太弱了(我认为;这是因为函数被限制为Hask
并且它比 弱Cat
)。有关更多信息,请参阅此。
Foldable
?现在至于Foldable
:存在fold
使用自定义二进制函数组合元素的 s 定义。现在您当然可以提供任何函数来组合元素,或者您可以使用现有的组合元素概念,即幺半群。再次请注意,这个幺半群仅限于集合幺半群,而不是幺半群的典型定义。
由于幺半群mappend
是关联的,foldl
并且foldr
产生相同的结果,这就是为什么幺半群的折叠可以简化为fold :: Monoid m, Foldable t => t m -> m
. 这是幺半群和可折叠之间的明显联系。
@danidiaz 已经指出了Applicative
,Monoid
和Foldable
使用Const
functor之间的联系Const a b = Const a
,它的应用实例要求 of 的第一个参数Const
是一个幺半群(没有pure
(mempty
不考虑undefined
))。
在我看来,比较 monad 和 foldable 有点牵强,因为 monad 比 foldable 更强大,因为 foldable 只能根据映射函数累积列表的值,但 monad 绑定可以在结构上改变上下文 ( a -> m b
)。
摘要:(>>=)
并且traverse
看起来很相似,因为它们都是函子的箭头映射,而foldMap
(几乎)是专门的traverse
.
在我们开始之前,需要解释一些术语。考虑fmap
:
fmap :: Functor f => (a -> b) -> (f a -> f b)
HaskellFunctor
是从Hask范畴(以 Haskell 函数为箭头的范畴)到自身的函子。在范畴论术语中,我们说 (specialised)fmap
是这个函子的箭头映射,因为它是函子中将箭头指向箭头的部分。(为了完整起见:函子由箭头映射和对象映射组成。在这种情况下,对象是 Haskell 类型,因此对象映射将类型转换为类型——更具体地说,a 的对象映射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以外的类别和非Functor
s 的函子。在这种情况下,我们将用适当的标识和组合、适当的箭头映射以及在一种情况下,用适当的箭头相等来替换id
和。(.)
fmap
=
从答案中更熟悉的部分开始,对于给定的 monad m
,a -> m b
函数(也称为 Kleisli 箭头)形成一个类别(的 Kleisli 类别m
),具有return
身份和(<=<)
组合。在这种情况下,三类法则只是单子法则:
f <=< return = f
return <=< f = f
h <=< (g <=< f) = (h <=< g) <=< f
现在,您询问了翻转绑定:
(=<<) :: Monad m => (a -> m b) -> (m a -> m b)
事实证明,这(=<<)
是从 Kleisli 范畴的函子到 Hask 的m
箭头映射。适用的函子定律(=<<)
相当于两个单子定律:
return =<< x = x -- right unit
(g <=< f) =<< x = g =<< (f =<< x) -- associativity
接下来,我们需要绕道而行Traversable
(答案末尾提供了本节结果证明的草图)。首先,我们注意到所有应用函子的a -> f b
函数一次获取(而不是每次一个,如指定 Kleisli 类别时)形成一个类别,具有身份和组合。为此,我们还必须采用更宽松的箭头相等,它忽略和样板(这只是必要的,因为我是用伪 Haskell 编写的,而不是正确的数学符号)。更准确地说,我们将考虑任何两个函数可以使用和的任何组合相互转换f
Identity
Compose . fmap g . f
Identity
Compose
Identity
Compose
同构为相等的箭头(或者,换句话说,我们不会区分a
and Identity a
,也不区分f (g a)
and Compose f g a
)。
让我们将该类别称为“可遍历类别”(因为我现在想不出更好的名称)。Applicative
在具体的 Haskell 术语中,此类别中的箭头是一个在任何先前存在的层“下方”添加额外上下文层的函数。现在,考虑traverse
:
traverse :: (Traversable t, Applicative f) => (a -> f b) -> (t a -> f (t b))
给定可遍历容器的选择,traverse
是函子从“可遍历类别”到自身的箭头映射。它的函子定律相当于可遍历定律。
简而言之,两者(=<<)
和traverse
都是fmap
涉及除Hask之外的类别的函子的类似物,因此它们的类型彼此有点相似也就不足为奇了。
我们仍然需要解释所有这些与foldMap
. 答案是foldMap
可以从中恢复traverse
(参见danidiaz 的答案——它使用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
is notFoldable
和foldMapDefault
is not ,这也为为什么 的类型类似于 of的类型以及传递性地类似于 offoldMap
的类型提供了一个体面的理由。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
当容器是时,和(它是 的超类)Foldable
之间存在关系。foldMap
Applicative
Monad
Foldable
有一个名为 的函数traverse_
,带有签名:
traverse_ :: Applicative f => (a -> f b) -> t a -> f ()
一种可能Applicative
是Constant
。要成为 Applicative,它需要“累加器”参数为Monoid
:
newtype Constant a b = Constant { getConstant :: a } -- no b value at the term level!
Monoid a => Applicative (Constant a)
例如:
gchi> Constant (Sum 1) <*> Constant (Sum 2) :: Constant (Sum Int) whatever
Constant (Sum {getSum = 3})
我们可以这样foldMap
定义:traverse_
Constant
foldMap' :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
foldMap' f = getConstant . traverse_ (Constant . f)
我们traverse_
用来遍历容器,用 来累积值Constant
,然后我们getConstant
用来摆脱新类型。