monad bind (>>=) 运算符更接近函数组合(链接)还是函数应用?

Is monad bind (>>=) operator closer to function composition (chaining) or function application?

在我读到的许多文章中,monad >>= 运算符是表示函数组合的一种方式。但对我来说它更接近于某种高级功能应用

($)   :: (a -> b) -> a -> b
(>>=) :: Monad m => m a -> (a -> m b) -> m b

对于组合我们有

(.)   :: (b -> c) -> (a -> b) -> a -> c
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c

请说明。

显然,>>= 不是表示函数组合 的方法。函数组合只需使用 . 即可完成。但是,我认为您阅读的任何文章也不是这个意思。

他们的意思是“升级”函数组合以直接与“单子函数”一起工作,即 a -> m b 形式的函数。此类函数的技术术语是 Kleisli arrows,实际上它们可以与 <=<>=> 组合。 (或者,您可以使用 Category instance,然后您也可以将它们与 .>>> 组合。)

然而,谈论箭头/类别往往容易混淆,尤其是对于初学者来说,就像 point-free definitions 普通函数常常令人困惑一样。幸运的是,Haskell 允许我们以一种更熟悉的风格来表达函数,这种风格着重于函数的 结果 ,而不是作为抽象态射的函数本身。它是用 lambda 抽象完成的:而不是

q = h . g . f

你可以写

q = (\x -> (\y -> (\z -> h z) (g y)) (f x))

...当然首选的样式是 (这只是 lambda 抽象的语法糖!)

q x = let y = f x
          z = g y
      in h z

请注意,在 lambda 表达式中,组合基本上是如何被应用程序取代的:

q = \x -> (\y -> (\z -> h z) $ g y) $ f x

适用于 Kleisli 箭,这意味着

q = h <=< g <=< f

你写

q = \x -> (\y -> (\z -> h z) =<< g y) =<< f x

使用翻转运算符或语法糖,这看起来当然更好:

q x = do y <- f x
         z <- g y
         h z

所以,确实,=<< 之于 <=< 就像 $ 之于 .。将其称为组合运算符仍然有意义的原因是,除了“应用于值”之外,>>= 运算符还执行关于 Kleisli 箭头组合的重要部分,函数组合不需要:加入一元层。


之所以有效,是因为 Hask 是一个 cartesian closed category, in particular a well-pointed category。在这样的类别中,从广义上讲,箭头可以通过应用于简单参数值时所有结果的集合来定义。

@adamse 评论说 let 并不是 lambda 抽象的真正语法糖。这在递归定义的情况下尤为重要,您不能直接用 lambda 编写。但是在像这里这样的简单情况下,let 确实表现得像 lambda 的语法糖,就像 do 符号是 lambda 和 >>= 的语法糖一样。 (顺便说一句,有一个允许递归 even in do notation 的扩展......它通过使用 fixed-point 组合器绕过了 lambda-restriction。)

举个例子,考虑一下:

($)                ::   (a -> b) ->   a ->   b
let g=g in (g  $)  ::                 a ->   b
            g      ::   (a -> b)
                                     _____
Functor f =>                        /     \
(<$>)              ::   (a -> b) -> f a -> f b
let g=g in (g <$>) ::               f a -> f b
            g      ::   (a -> b) 
                       ___________________
Applicative f =>      /             /     \
(<*>)              :: f (a -> b) -> f a -> f b
let h=h in (h <*>) ::               f a -> f b
            h      :: f (a -> b)
                             _____________
Monad m =>                  /.------.     \
(=<<)              :: (a -> m b) -> m a -> m b
let k=k in (k =<<) ::               m a -> m b
            k      :: (a -> m b)

所以是的,(g <$>)(h <*>)(k =<<) 中的每一个都是某种函数应用程序,被提升为 Functor、Applicative Functor 或 Monad "context"。而(g $)只是一种常规函数的常规应用。

使用 Functors,函数对整个 thingf 组件没有影响。他们严格在内部工作,不能影响 "wrapping".

使用 Applicatives,函数包装在 f 中,该包装与参数(作为应用程序的一部分)的包装相结合以产生结果的包装。

对于 Monad,函数本身现在会产生包装结果,以某种方式从包装参数中提取它们的参数(作为应用程序的一部分)。

我们可以将这三个运算符看作是函数上的某种标记,就像数学家喜欢写 f'f^f* (在原始作品中作者 Eugenio Moggi(1) f* 正是所使用的,表示提升的函数 (f =<<)).

当然,使用提升的函数 :: f a -> f b,我们可以将它们链接起来,因为现在类型已对齐。晋升是允许作文的。


(1) "Notions of computation and monads",Eugenio Moggi,1991 年 7 月。

  • 关于组合性的更多信息,附上图片:Monads with Join() instead of Bind()

所以函子是"magically working inside""the pipes";适用的是"prefabricated pipes built from components in advance";单子是 "building pipe networks as we go"。插图: