为什么绑定操作符(>>=)的定义是这样的?

问题描述 投票:1回答:1

我学习Haskell已经有几个星期了(只是为了好玩),刚刚看了Brian Beckman的精彩的《Haskell》。单项式介绍视频. 他激励单项式的动机是需要创建一个更通用的组成运算符。 按照这个思路,如果我有两个函数。

f :: a -> b
g :: b -> c

组成算子应该满足

h = g . f :: a -> c

由此我可以推断出正确的类型。. 操作符。

(.) : (b -> c) -> (a -> b) -> (a -> c)

当涉及到单项式时,假设我有两个函数。

f :: a -> m b
g :: b -> m c

在我看来,自然而然的选择是 定义一个广义的构成运算符,其工作原理如下:

h = f >>= g :: a -> m c

这样的话 >>= 操作符的类型签名为。

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

但实际上这个操作符的定义似乎是这样的:

h a = (f a) >>= g :: m c

于是

(>>=) : m b -> (b -> m c) -> m c

谁能解释一下选择bind这个定义背后的原因? 我假设这两个选择之间有一些简单的联系,其中一个可以用另一个来表达,但我现在没有看到。

haskell bind monads
1个回答
50
投票

谁能解释一下选择bind这个定义背后的原因?

当然可以,而且几乎和你的理由完全一样。只是... ... 我们想要一个更通用的... 申请 操作符,而不是一个更通用的组成操作符。如果你做过很多(任何)无点程序设计,你会立即认识到为什么:与有点程序相比,无点程序很难写,而且令人难以置信的难读。比如说

h x y = f (g x y)

对于函数应用来说,这是完全直接的。只用函数组成的版本是什么样子的?

h = (f .) . g

如果你第一次看到这个的时候,不用停下来盯着看一两分钟,你可能真的是一台电脑。

所以,不管出于什么原因:我们的大脑被赋予了更好的工作能力,名字和函数应用一出,就能更好的工作。所以你剩下的参数是这样的,但是用应用代替了组成。如果我有一个函数和一个参数。

f :: a -> b
x :: a

应用运算符应该满足

h = x & f :: b

由此我可以推断出正确的类型。& 操作符。

(&) :: a -> (a -> b) -> b

当涉及到单项式时, 假设我的函数和参数是单项式的:

f :: a -> m b
x :: m a

自然而然的选择是定义一个广义的应用运算符 它的工作原理如下:

h = x >>= f :: m b

在这种情况下 >>= 操作符的类型签名为

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

27
投票

你可以搜索你的操作员 在胡戈尔,看到它被称为 (>=>). 其定义为: (>>=)很简单:

f >=> g = \x -> f x >>= g

从某种意义上讲, (>=>) 是更好地体现了概括作文的思想,但我认为 (>>=) 作为一个基元操作符更好用,只是因为它在更多的情况下很实用,而且更容易与do-notation联系起来。


20
投票

(>>=) 组成运算符。是一个应用运算符。

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

还有 (=<<) (自 Control.Monad),对应的是更常见的应用运算符。($):

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

在组成方面,我们有两个 (<=<)(>=>) (又来自 Control.Monad,第一种是完全类似于 (.):

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

((>=>) 只是 (<=<) 并翻转其参数。(>=>) = flip (<=<))


当我们在比较类型时,你可能想看看如何在 fmap 适合。

($)   ::              (a ->   b) ->   a ->   b
fmap  :: Functor f => (a ->   b) -> f a -> f b
(=<<) :: Monad m   => (a -> m b) -> m a -> m b

($)fmap 采用相同类型的函数,但应用于不同类型的参数。

fmap(=<<) 采用不同类型的函数,但都应用于同一类型的参数(虽然方式不同)。


7
投票

我同意从以下方面思考 ( >=> ) :: ( a -> m b ) -> ( b -> m c ) -> ( a -> m c) 往往感觉更自然,因为它更接近于通常的功能构成,事实上,它 Kleisli类别中的组成。从这个角度来看,Haskell的许多单体实例其实更容易理解。

Haskell之所以选择 ( >>= ) :: m a -> ( a -> m b) -> m b 可能是这个定义在某种程度上是最普遍的定义。这两个定义都是: >=>join :: m ( m x ) -> m x 可还原为 >>=:

( >=> ) f g x = f x >>= g

join mmx = mmx >>= id

如果你加上 return :: x -> m x 也可以推导出 fmap :: ( a -> b ) -> m a -> m b (Functor)和 ( <*> ) :: m ( a -> b ) -> m a -> m b (适用):

fmap f ma = ma >>= ( return . f )

( <*> ) mab ma =
    mab >>= \f ->
    ma  >>= \a ->
    return ( f a )
© www.soinside.com 2019 - 2024. All rights reserved.