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

Mic*_*ann 9 monads haskell category-theory

我的印象是(>>=)(由Haskell使用)和join(由数学家首选)是“相等的”,因为一个人可以用另一个来写:

import Control.Monad (join)

join x = x >>= id
x >>= f = join (fmap f x)
Run Code Online (Sandbox Code Playgroud)

另外,每个monad都是函子,因为bind可以用来代替fmap

fmap f x = x >>= (return . f)
Run Code Online (Sandbox Code Playgroud)

我有以下问题:

  1. 是否有一个(非递归)的定义fmap来讲join?(fmap f x = join $ fmap (return . f) x根据上面的方程式,但是是递归的。)

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

  3. bind不是更“强大” join?“更强大”意味着什么?

dup*_*ode 6

是否有一个[定义]fmap在以下方面join

不,没有。这可以通过尝试这样做来证明。假设我们有一个任意类型的构造T函数和函数:

returnT :: a -> T a
joinT :: T (T a) -> T a
Run Code Online (Sandbox Code Playgroud)

仅从这些数据,我们想定义:

fmapT :: (a -> b) -> T a -> T b
Run Code Online (Sandbox Code Playgroud)

所以让我们勾画它:

fmapT :: (a -> b) -> T a -> T b
fmapT f ta = tb
    where
    tb = undefined  -- tb :: T b
Run Code Online (Sandbox Code Playgroud)

我们需要以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)
Run Code Online (Sandbox Code Playgroud)

它只是把罐子踢倒了,因为T (T b)在这种情况下需要一个值是没有改进的。

我们可能会尝试returnT

fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT b
    where
    b = undefined  -- b :: b
Run Code Online (Sandbox Code Playgroud)

现在我们需要一个b值。唯一可以给我们的是f

fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f a)
    where
    a = undefined  -- a :: a
Run Code Online (Sandbox Code Playgroud)

现在我们被困住了:没有什么可以给我们一个a. 我们已经用尽了所有可用的可能性,所以fmapT不能用这样的术语来定义。

题外话:使用这样的函数来作弊是不够的:

extractT :: T a -> a
Run Code Online (Sandbox Code Playgroud)

使用extractT,我们可能会尝试a = extractT ta,导致:

fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f (extractT ta))
Run Code Online (Sandbox Code Playgroud)

然而,仅仅fmapT拥有正确的类型是不够的:它还必须遵循函子定律。尤其fmapT id = id应该持有。有了这个定义,fmapT idis returnT . extractT,一般来说,不是id(大多数函子都是两者的实例MonadComonad作为例子)。


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

“每个 monad 都是函子”是一个假设,或者更准确地说,是 monad 定义的一部分。要选择一个任意的插图,这里是 Emily Riehl, Category Theory In Context,p。154:

定义 5.1.1。甲单子上的类C由

  • 一个内函子T : C ? C,

  • 一个单元自然转化:1 Ť,并

  • 一个乘法自然转化T 2牛逼

以便以下图表在 C C中交换:[monad 定律的图表]

因此,根据定义,单子包含一个内函子。对于T实例化的 Haskell 类型构造函数,Monad该内函子的对象映射是T它本身,而态射映射是它的fmap。这T将是一个Functor实例,因此将有一个fmap, is,在当代 Haskell 中,保证Applicative(并且,通过扩展,Functor)是 的超类Monad

不过,这就是故事的全部吗?就 Haskell 而言。我们知道liftM存在,而且在不远的过去Functor不是 的超类Monad。这两个事实仅仅是哈斯克尔主义吗?不完全的。在经典论文Notions of computing and monads 中,Eugenio Moggi 揭示了以下定义(第 3 页):

定义 1.2 ([Man76])类别C上的Kleisli 三元是三元组(T, ?, _*),其中T : Obj( C ) ? 对象( C ), ? :一个?TA一个?Obj( C ), f* : TA ? TBf : A ? TB和以下等式成立:

  • ? A * = id T A
  • ? 一个; f* = f   为   f : A ? 结核病
  • F*; g* = (f; g*)*   对于   f : A ? TB   和   g : B ? 技术委员会

这里最重要的细节是,Ť被呈现为在类别仅仅是一个对象映射Ç,而不是如在endofunctor Ç。在Hask类别中工作,这相当于采用类型构造函数T而不预先假定它是一个Functor实例。在代码中,我们可以这样写:

class KleisliTriple t where
    return :: a -> t a
    (=<<) :: (a -> t b) -> t a -> t b

-- (return =<<) = id
-- (f =<<) . return = f
-- (g =<<) . (f =<<) = ((g =<<) . f =<<)
Run Code Online (Sandbox Code Playgroud)

将绑定放在一边,这是MonadHaskell 中的 pre-AMP 定义。不出所料,Moggi 的论文很快就表明“在 Kleisli 三元组和单子之间存在一一对应的关系”(第 5 页),并建立了T可以扩展到一个内函子(在 Haskell 中,这一步相当于定义态射映射liftM f m = return . f =<< m,然后显示它遵循函子定律)。

总而言之,如果你写的合法定义return(>>=)没有预先假定fmap,你确实得到了合法的执行Functor结果。“Kleisli 三元组和 monad 之间存在一一对应”是 Kleisli 三元组定义的结果,而“monad 涉及一个内函子”是 monad 定义的一部分。很容易考虑将 Haskellers 在编写Monad实例时所做的描述为“设置 Klesili 三元组”而不是“设置单子”是否更准确,但我会避免陷入术语学究的困境 - - 无论如何,现在这Functor 一个超类,Monad没有实际理由担心这一点。


bind不是更“强大” join?“更强大”是什么意思?

套路问题!

从表面上看,答案是肯定的,在某种程度上,与一起return(>>=)使得它可以实现fmap(通过liftM,如上所述),而join不会。但是,我认为坚持这种区分是不值得的。为什么这样?因为单子定律。就像它没有意义谈论一个合法的 (>>=)没有预先假定return,它没有意义谈论合法join不pressuposingreturn fmap

人们可能会觉得我通过使用它们来捆绑MonadFunctor以这种方式过于重视法律。确实存在涉及两个类的法律案例,并且仅适用于实例化它们的类型。Foldable提供了一个很好的例子:我们可以发现以下规律Traversable文档

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

在这个Foldable例子中,foldMap应该等价于用一个常数应用函子 ( foldMapDefault)进行遍历。

这个特定的定律并不总是适用不是问题,因为我们不需要它来表征是什么Foldable(替代方案包括“aFoldable是一个容器,我们可以从中提取一些元素序列”,以及“aFoldable是一个容器可以转换为其元素类型的自由幺半群”)。然而,对于 monad 法则,它不是那样的:类的含义与所有三个 monad 法则密不可分。


mb2*_*b21 5

一个monad的定义如下

  • 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还是使用join和定义特定的monad fmap。如果您看到第二个定义,则更容易理解该语句,仅此而已。
  3. 是的,bindjoin。如果您用“强大”来表示它具有定义monad的能力(总是与结合使用),则它与“强大” 完全一样,join并且fmap组合在一起return

有关直觉,请参见此答案 - bind允许您将策略/计划/计算(在上下文中)组合或链接在一起。例如,让我们使用Maybe上下文(或Maybemonad):

?: let plusOne x = Just (x + 1)
?: Just 3 >>= plusOne
Just 4
Run Code Online (Sandbox Code Playgroud)

fmap 还可以让您将上下文中的计算链接在一起,但是会以增加每一步的嵌套为代价。[1]

?: fmap plusOne (Just 3)
Just (Just 4)
Run Code Online (Sandbox Code Playgroud)

这就是为什么您需要join:将两个嵌套层次压缩成一个层次。记得:

join :: m (m a) -> m a
Run Code Online (Sandbox Code Playgroud)

仅执行挤压步骤不会使您走得太远。您还fmap需要一个monad –和returnJust在上面的示例中。

[1]:fmap并且(>>=)不要以相同的顺序接受他们的两个论点,但是不要让这混淆了您。

  • 值得强调的是,单子始终是一个内函子。通用函子在类别之间移动。一个单子总是将一个类别映射到它自己。Haskell `Functor` 自动是一个 endofunctor。这在“bind”的定义中也很明显,其中态射从“a”到“mb”:因此两者必须属于同一类别。值得注意的是,单子的第三个定义是用 Kleisli 箭头来表示的,Haskell `&gt;=&gt;`,它也需要函数性。 (2认同)