我现在已经研究Haskell好几个星期了(只是为了好玩),刚刚看了Brian Beckman介绍单子的很棒的视频。 他用创建一个更通用的合成运算符的需求来激励单子。 按照这个思路,如果我有两个功能:
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定义的选择背后的原因吗? 我假设这两个选项之间有某种简单的联系,其中一个可以用另一个来表达,但我现在还没看到。
有人能解释一下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
您可以在Hoogle上搜索您的操作符,并看到它名为(>=>)
。 它用(>>=)
定义非常简单:
f >=> g = \x -> f x >>= g
在某种意义上,(>=>)
更好地反映了概括组合的思想,但我认为(>>=)
作为一个基本运算符更好地工作,因为它在更多的情况下更实用,并且更容易与do-notation相关联。
我同意用(>=>)::(A->m b)->来思考; (b->m c)->; (A->m,c)
通常感觉更自然,因为它更接近于通常的函数组合,实际上它是Kleisli类别中的组合。 Haskell的很多单子实例,从这个角度来看,其实更容易理解。
Haskell选择(>>=)::m a->的一个原因; (a->m b)->; Mb
可能是这个定义在某种程度上是最普遍的定义。 >=>
和join::m(m x)->; mx
可以简化为>>>=
:
( >=> ) f g x = f x >>= g
join mmx = mmx >>= id
如果添加,返回::x->; mx
到混合时,也可以派生fmap::(a->b)->>; m a->; mb
(函子)和(<*>)::m(a->b)->; m a->; m B
(适用):
fmap f ma = ma >>= ( return . f )
( <*> ) mab ma =
mab >>= \f ->
ma >>= \a ->
return ( f a )