我的印象是(>>=)
(由Haskell使用)和join
(由数学家首选)是“相等的”,因为一个人可以用另一个写一个:
import Control.Monad (join)
join x = x >>= id
(>>=) x f = join (fmap f x)
由于bind
可以用来代替fmap
,因此另外every monad is a functor:
fmap f x = x >>= (return . f)
我有以下问题:
在fmap
方面是否有join
的(非递归)定义? (fmap f x = join $ fmap (return . f) x
遵循上面的公式,但是是递归的。)
在使用bind
(在monad的定义中)时,“每个monad都是函子”的结论,但是在使用join
时是假设吗?
bind
比join
更“强大”吗? “更强大”意味着什么?
答案 0 :(得分:5)
一个单子可以是either defined in terms of:
return :: a -> m a
bind :: m a -> (a -> m b) -> m b
或在以下方面:
return :: a -> m a
fmap :: (a -> b) -> m a -> m b
join :: m (m a) -> m a
对您的问题:
fmap
来定义join
,因为否则我们可以从上面的第二个列表中删除fmap
。bind
还是join
和fmap
来定义特定的monad。 。如果看到第二个定义,则更容易理解该语句,仅此而已。bind
比join
更“强大”。与join
和fmap
组合起来的“功能强大”完全一样,如果您用“功能强大”的意思是它具有定义monad的能力(始终与return
组合使用)。 / li>
有关直觉,请参见this answer – bind
允许您将策略/计划/计算(在上下文中)组合或链接在一起。例如,让我们使用Maybe
上下文(或Maybe
monad):
λ: let plusOne x = Just (x + 1)
λ: Just 3 >>= plusOne
Just 4
fmap
还使您可以将上下文中的计算链接在一起,但是以增加每一步的嵌套为代价。[1]
λ: fmap plusOne (Just 3)
Just (Just 4)
这就是为什么您需要join
:将两个嵌套层次压缩成一个层次的原因。记住:
join :: m (m a) -> m a
只有挤压步骤不会使您走得很远。您还需要fmap
来拥有单子-和return
,在上面的示例中为Just
。
[1]:fmap
和(>>=)
的两个参数使用的顺序不同,但不要让它们混淆。
答案 1 :(得分:2)
根据
fmap
是否有join
的[定义]?
不,没有。可以通过尝试证明这一点。假设我们得到了一个任意类型的构造函数T
和函数:
returnT :: a -> T a
joinT :: T (T a) -> a
仅根据这些数据,我们要定义:
fmapT :: (a -> b) -> T a -> T b
所以让我们来勾勒一下:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = tb
where
tb = undefined -- tb :: T b
我们需要以某种方式获取类型T b
的值。 ta :: T a
本身不会起作用,因此我们需要产生T b
值的函数。仅有的两个候选人是joinT
和returnT
。 joinT
没有帮助:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = joinT ttb
where
ttb = undefined -- ttb :: T (T b)
它只是将罐子踢倒了,因为在这种情况下需要一个T (T b)
值并没有改善。
我们可以改用returnT
:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT b
where
b = undefined -- b :: b
现在,我们需要一个b
值。唯一可以给我们一个的是f
:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f a)
where
a = undefined -- a :: a
现在我们被困住了:没有什么可以给我们a
。我们已经用尽了所有可能的可能性,因此fmapT
不能用这样的术语来定义。
离题:使用这样的函数作弊是不够的:
extractT :: T a -> a
使用extractT
,我们可以尝试a = extractT ta
,导致:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f (extractT ta))
但是,fmapT
拥有正确的类型还不够:它还必须遵循函子定律。特别是fmapT id = id
应该成立。使用此定义,fmapT id
是returnT . extractT
,通常不是id
(作为Monad
和Comonad
的实例的大多数函子都作为示例)。
在使用
bind
(在monad的定义中)时,“每个monad都是函子”的结论,但是在使用join
时是假设吗?
“每个monad都是函子”是一个假设,或更准确地说,是monad定义的一部分。为了选择一个任意的例子,这里是Emily Riehl,Category Theory In Context,第p。 154:
定义5.1.1。 C类上的 monad 由
组成
终结符 T :C→C,
一个单位自然转换η:1 C ⇒ T ,并且
a 乘法自然变换μ: T 2 ⇒ T ,
以使下图在C C 中转换:[monad定律图]
因此,从定义上讲,一个monad包含一个endofunctor。对于实例化T
的Haskell类型构造函数Monad
,该endofunctor的对象映射是T
本身,而射晶映射是其fmap
。在现代Haskell中,T
将是一个Functor
实例,因此将有一个fmap
,并由Applicative
(并且通过扩展{{1} })是Functor
的超类。
那是整个故事吗?就Haskell而言。我们知道liftM
存在,而且在不那么遥远的过去Monad
并不是Functor
的超类。那两个事实仅仅是哈斯克尔主义吗?不完全的。在经典论文Notions of computation and monads中,Eugenio Moggi提出了以下定义(p。3):
定义1.2([Man76])在类别 C 上的 Kleisli三元组是三元组(T,η,_ *),其中 T :Obj( C )→Obj( C < / strong>),η A :A→TA 表示 A ∈Obj( C ), f *:TA→TB 表示 f:A→TB ,以下等式成立:
- η A * = id TA
- η A ; f * = f 表示 f:A→T B
- f *; g * =(f; g *)* 表示 f:A→T B 和 g:B→T C
这里的重要细节是, T 在类别 C 中仅作为对象映射出现,而在< strong> C 。在 Hask 类别中工作,就等于采用类型构造器Monad
,而不必假定它是T
实例。在代码中,我们可以这样写:
Functor
抛开绑定,这是Haskell中class KleisliTriple t where
return :: a -> t a
(=<<) :: (a -> t b) -> t a -> t b
-- (return =<<) = id
-- (f =<<) . return = f
-- (g =<<) . (f =<<) = ((g =<<) . f =<<)
的AMP之前的定义。毫不奇怪,Moggi的论文很快就表明“ Kleisli三元组和monad之间存在一对一的对应关系”(第5页),沿着 T 的扩展方式建立到endofunctor(在Haskell中,此步骤等同于定义射态映射Monad
,然后按照函子定律显示它)。
总而言之,如果您编写liftM f m = return . f =<< m
和return
的合法定义而不以(>>=)
为前提,那么实际上确实可以合法地实现fmap
。 “ Kleisli三元组和monad之间存在一对一的对应关系”是Kleisli三元组定义的结果,而“ monad包含endofunctor”是monad定义的一部分。考虑将Haskellers在编写Functor
实例时所做的操作描述为“设置一个Klesili三元组”而不是“设置一个monad”是否更准确,但我会避免担心。废除了术语学方法-无论如何,既然Monad
是Functor
的超类,就没有实际的理由担心这一点。
Monad
比bind
更“强大”吗? “更强大”意味着什么?
技巧问题!
从表面上看,答案是肯定的,只要与join
,return
一起实现(>>=)
(通过fmap
,如上所述),而liftM
则没有。但是,我认为坚持这种区别并不值得。为什么这样?因为单子法。就像在没有join
的前提下谈论合法 (>>=)
一样没有意义,在没有前提的情况下谈论合法的return
也没有意义加上join
和 return
。
通过使用这样的方式将fmap
和Monad
绑定在一起,我可能会觉得我对法律过于重视。的确有一些案例涉及两个类别,并且仅适用于实例化两个类别的类型。 Functor
提供了一个很好的例子:我们可以在the Traversable
documentation中找到以下定律:
超类实例应满足以下条件:[...]
在
Foldable
实例中,Foldable
应该等效于使用常量应用函子(foldMap
)进行遍历。
这个特定的法律并不总是适用的,这不是问题,因为我们不需要它来描述foldMapDefault
是什么(替代方案包括“ Foldable
是一个容器,我们可以从中提取元素的某些序列”,然后“ Foldable
是一个容器,可以根据其元素类型将其转换为自由monoid”。但是,对于单子法则,并不是那样的:类的含义与这三个单子法律密不可分。