为什么绑定运算符 (>>=) 按原样定义?
Why is the bind operator (>>=) defined as it is?
我已经研究 Haskell 几个星期了(只是为了好玩),刚刚看了 Brian Beckman 的精彩 video introducing monads。他激励 monad 需要创建一个更通用的组合运算符。按照这个思路,如果我有两个函数:
f :: a -> b
g :: b -> c
合成算子应满足
h = g . f :: a -> c
由此我可以推断出 .
运算符的正确类型:
(.) : (b -> c) -> (a -> b) -> (a -> c)
说到 monad,假设我有两个函数:
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
有人可以解释一下选择绑定定义背后的原因吗?我假设这两个选择之间存在一些简单的联系,其中一个可以用另一个来表达,但我目前没有看到。
您可以搜索您的运营商on Hoogle, and see that it is called (>=>)
. Its definition in terms of (>>=)
is quite simple:
f >=> g = \x -> f x >>= g
在某种意义上 (>=>)
更好地反映了泛化组合的想法,但我认为 (>>=)
作为原始运算符效果更好,因为它在更多情况下实用,并且更容易关联做符号。
Could someone explain the reasoning behind this choice of definition for bind?
当然可以,这几乎与您的推理完全相同。只是...我们想要一个更通用的 application 运算符,而不是更通用的组合运算符。如果您做过很多(任何)无点编程,您会立即认识到原因:与有点程序相比,无点程序很难编写,而且难以阅读。例如:
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
我同意用 ( >=> ) :: ( a -> m b ) -> ( b -> m c ) -> ( a -> m c)
来思考通常感觉更自然,因为它更接近通常的函数组合,事实上它 是 Kleisli 类别中的 组合。 Haskell的很多monad实例从这个角度来看其实更容易理解。
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
(函子)和 ( <*> ) :: m ( a -> b ) -> m a -> m b
(应用):
fmap f ma = ma >>= ( return . f )
( <*> ) mab ma =
mab >>= \f ->
ma >>= \a ->
return ( f a )
(>>=)
是 不是 组合运算符。是应用运营商。
(&) :: 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
和 (=<<)
采用不同类型的函数,但将它们都应用于同一类型的参数(尽管采用不同的方式)。
我已经研究 Haskell 几个星期了(只是为了好玩),刚刚看了 Brian Beckman 的精彩 video introducing monads。他激励 monad 需要创建一个更通用的组合运算符。按照这个思路,如果我有两个函数:
f :: a -> b
g :: b -> c
合成算子应满足
h = g . f :: a -> c
由此我可以推断出 .
运算符的正确类型:
(.) : (b -> c) -> (a -> b) -> (a -> c)
说到 monad,假设我有两个函数:
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
有人可以解释一下选择绑定定义背后的原因吗?我假设这两个选择之间存在一些简单的联系,其中一个可以用另一个来表达,但我目前没有看到。
您可以搜索您的运营商on Hoogle, and see that it is called (>=>)
. Its definition in terms of (>>=)
is quite simple:
f >=> g = \x -> f x >>= g
在某种意义上 (>=>)
更好地反映了泛化组合的想法,但我认为 (>>=)
作为原始运算符效果更好,因为它在更多情况下实用,并且更容易关联做符号。
Could someone explain the reasoning behind this choice of definition for bind?
当然可以,这几乎与您的推理完全相同。只是...我们想要一个更通用的 application 运算符,而不是更通用的组合运算符。如果您做过很多(任何)无点编程,您会立即认识到原因:与有点程序相比,无点程序很难编写,而且难以阅读。例如:
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
我同意用 ( >=> ) :: ( a -> m b ) -> ( b -> m c ) -> ( a -> m c)
来思考通常感觉更自然,因为它更接近通常的函数组合,事实上它 是 Kleisli 类别中的 组合。 Haskell的很多monad实例从这个角度来看其实更容易理解。
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
(函子)和 ( <*> ) :: m ( a -> b ) -> m a -> m b
(应用):
fmap f ma = ma >>= ( return . f )
( <*> ) mab ma =
mab >>= \f ->
ma >>= \a ->
return ( f a )
(>>=)
是 不是 组合运算符。是应用运营商。
(&) :: 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
和 (=<<)
采用不同类型的函数,但将它们都应用于同一类型的参数(尽管采用不同的方式)。