考虑foldMap
foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
这与“绑定”非常相似,只是交换了参数:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
在我看来,Foldable
,Monoid
和Monad
之间必然存在某种关系,但我无法在超类中找到它。据推测,我可以将其中的一个或两个转换为另一个,但我不确定如何。
这种关系可以详细说明吗?
答案 0 :(得分:24)
Monoid
和Monad
monad只是endofunctors类别中的幺半群,[...]
让我们从一个幺半群开始。集合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
请注意,monoid不限于集合,类别(monad)类别Cat
中也存在幺半群等。基本上任何时候你都有一个关联二进制操作和它的标识。
现在是monad,它是endofunctors类别中的" monoid"具有以下属性:
它是一个endofunctor,这意味着它在Haskell类型的类别* -> *
中具有类型Hask
。
现在,为了更进一步,你必须知道我将在这里解释的一些类别理论:给定两个仿函数F
和G
,存在F
的自然变换到G
如果存在函数α
,则每个F a
都可以映射到G a
。 α
可以是多对一的,但必须映射F a
的每个元素。粗略地说,自然变换是算子之间的函数。
现在在类别理论中,两个类别之间可以存在许多仿函数。在简化的视图中,可以说我们甚至不关心哪些仿函数从何处映射到哪里,我们只关心它们之间的自然转换。
回到monad,我们现在可以看到endofunctors类别中的" monoid"必须进行两次自然变革。让我们调用我们的monad endofunctor M
:
从身份(endo)仿函数到monad的自然转换:
η :: 1 -> M -- this is return
从两个monad的位置自然转变并产生第三个:
μ :: 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
因此,monad是endofunctors类别中monoid的特例。你不能在正常的Haskell中为monad编写一个monoid实例,因为Haskell的组合概念太弱了(我认为;这是因为函数仅限于Hask
而且它是#{1}}弱于Cat
)。有关详细信息,请参阅this。
Foldable
怎么样?现在和Foldable
一样:fold
的定义使用自定义二元函数来组合元素。现在你可以提供任何组合元素的功能,或者你可以使用现有的组合元素概念,即monoid。再次请注意,这个幺半群仅限于幺半群,不幺半群的catorical定义。
由于幺半群mappend
是关联的,foldl
和foldr
会产生相同的结果,这就是为什么幺半群的折叠可以减少到fold :: Monoid m, Foldable t => t m -> m
。这是monoid和foldable之间的明显联系。
@danidiaz已使用Applicative
仿函数Monoid
指出Foldable
,Const
和Const a b = Const a
之间的关联,其应用实例需要第一个参数Const
为幺半群(没有pure
没有mempty
(忽略undefined
)。
比较monad和foldable在我看来有点拉伸,因为monad比可折叠更强大,因为foldable只能根据映射函数累积列表的值,但是monad绑定可以从结构上改变了背景(a -> m b
)。
答案 1 :(得分:9)
摘要:(>>=)
和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 以外的类别以及非Functor
s的仿函数。在这种情况下,我们会使用适当的标识和组合替换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 的仿函数的箭头映射。适用于(=<<)
的仿函数法则相当于两个monad定律:
return =<< x = x -- right unit
(g <=< f) =<< x = g =<< (f =<< x) -- associativity
接下来,我们需要绕过Traversable
(在答案的最后提供了本节结果证明的草图)。首先,我们注意到所有应用仿函数a -> f b
的{{1}}函数一次采取(而不是每次一个,如指定Kleisli类别时)形成一个类别,f
为身份,Identity
为组合。为了实现这一点,我们还必须采用更宽松的箭头相等,忽略Compose . fmap g . f
和Identity
样板(这是必要的,因为我在伪Haskell中写这个,而不是适当的数学符号)。更准确地说,我们将考虑任何两个可以使用Identity
和Compose
同构的任意组合相互转换的函数作为相等的箭头(换句话说,我们不会区分{{1 }和Compose
,以及a
和Identity a
之间。
让我们将该类别称为“可遍历类别”(因为我现在想不出更好的名字)。在具体的Haskell术语中,此类别中的箭头是一个函数,它在任何先前存在的层下面添加了f (g a)
上下文“额外”层。现在,考虑Compose f g a
:
Applicative
给定可遍历容器的选择,traverse
是仿函数从“可遍历类别”到其自身的箭头映射。它的仿函数法则适用于可遍及的法则。
简而言之,对于涉及 Hask 以外的类别的仿函数,traverse :: (Traversable t, Applicative f) => (a -> f b) -> (t a -> f (t b))
和traverse
都是(=<<)
的类似物,因此它们的类型就不足为奇了有点相似。
我们仍然需要解释所有这些与traverse
有什么关系。答案是fmap
可以从foldMap
恢复(参见danidiaz's answer - 它使用foldMap
,但由于应用仿函数为traverse
,结果为基本上相同):
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
且foldMapDefault
不是Foldable
,这也为foldMap
的类型与foldMap
的类型提供了合理的理由。和,traverse
。
作为最后一次观察,请注意,某些(=<<)
Const m
的“可遍历类别”的箭头与应用仿函数Monoid
执行而非形成子类别,因为没有身份,除非m
是应用函子的可能选择之一。这可能意味着从这个答案的角度来看,没有任何其他值得关注的Identity
。给出子类别的唯一选择的applicative functor是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
答案 2 :(得分:7)
当容器为Foldable
时,foldMap
和Applicative
(Monad
的超类)之间存在关联。
Foldable
有一个名为traverse_
的函数,其签名为:
traverse_ :: Applicative f => (a -> f b) -> t a -> f ()
一个可能的Applicative
是Constant
。要成为一个应用者,它需要&#34;累加器&#34;参数为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
删除新类型。