绑定和连接之间是什么关系?

时间:2019-05-31 08:19:22

标签: haskell monads category-theory

我的印象是(>>=)(由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)

我有以下问题:

  1. fmap方面是否有join的(非递归)定义? (fmap f x = join $ fmap (return . f) x遵循上面的公式,但是是递归的。)

  2. 在使用bind(在monad的定义中)时,“每个monad都是函子”的结论,但是在使用join时是假设吗?

  3. bindjoin更“强大”吗? “更强大”意味着什么?

2 个答案:

答案 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

对您的问题:

  1. 否,我们无法根据fmap来定义join,因为否则我们可以从上面的第二个列表中删除fmap
  2. 不,“每个monad都是函子”通常是关于monad的陈述,无论您是根据bind还是joinfmap来定义特定的monad。 。如果看到第二个定义,则更容易理解该语句,仅此而已。
  3. 是的,bindjoin更“强大”。与joinfmap组合起来的“功能强大”完全一样,如果您用“功能强大”的意思是它具有定义monad的能力(始终与return组合使用)。 / li>

有关直觉,请参见this answerbind允许您将策略/计划/计算(在上下文中)组合或链接在一起。例如,让我们使用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值的函数。仅有的两个候选人是joinTreturnTjoinT没有帮助:

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 idreturnT . extractT,通常不是id(作为MonadComonad的实例的大多数函子都作为示例)。


  

在使用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 =<< mreturn的合法定义而不以(>>=)为前提,那么实际上确实可以合法地实现fmap。 “ Kleisli三元组和monad之间存在一对一的对应关系”是Kleisli三元组定义的结果,而“ monad包含endofunctor”是monad定义的一部分。考虑将Haskellers在编写Functor实例时所做的操作描述为“设置一个Klesili三元组”而不是“设置一个monad”是否更准确,但我会避免担心。废除了术语学方法-无论如何,既然Monad Functor的超类,就没有实际的理由担心这一点。


  

Monadbind更“强大”吗? “更强大”意味着什么?

技巧问题!

从表面上看,答案是肯定的,只要与joinreturn一起实现(>>=)(通过fmap,如上所述),而liftM则没有。但是,我认为坚持这种区别并不值得。为什么这样?因为单子法。就像在没有join的前提下谈论合法 (>>=)一样没有意义,在没有前提的情况下谈论合法的return也没有意义加上join return

通过使用这样的方式将fmapMonad绑定在一起,我可能会觉得我对法律过于重视。的确有一些案例涉及两个类别,并且仅适用于实例化两个类别的类型。 Functor提供了一个很好的例子:我们可以在the Traversable documentation中找到以下定律:

  

超类实例应满足以下条件:[...]

     

Foldable实例中,Foldable应该等效于使用常量应用函子(foldMap)进行遍历。

这个特定的法律并不总是适用的,这不是问题,因为我们不需要它来描述foldMapDefault是什么(替代方案包括“ Foldable是一个容器,我们可以从中提取元素的某些序列”,然后“ Foldable是一个容器,可以根据其元素类型将其转换为自由monoid”。但是,对于单子法则,并不是那样的:类的含义与这三个单子法律密不可分。