显然,>>=
不是表示函数组合的方式。函数组合只需使用.
。不过,我认为你读过的任何文章都没有这个意思。
他们的意思是将函数组合“升级”到直接使用“单子函数”,即形如a -> m b
的函数。这种函数的技术术语是Kleisli箭头,确实可以使用<=<
或 >=>
进行组合。(或者,您可以使用Category
实例,然后也可以使用.
或 >>>
进行组合。)
但是,谈论箭头/范畴往往会使初学者感到困惑,就像普通函数的无点定义一样常常令人困惑。幸运的是,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是一个笛卡尔闭范畴,特别是好指向范畴。在这样的范畴中,箭头可以被简单参数值应用所得到的所有结果的集合广义地定义。
‡@adamse指出,let
实际上并不是lambda抽象的语法糖。这在递归定义的情况下尤其重要,因为你不能直接使用lambda来编写递归定义。但是在像这里这样的简单情况下,let
的行为就像lambda的语法糖一样,就像do
符号是lambda和>>=
的语法糖一样。(顺便说一句,有一个扩展可以允许递归即使在do
符号中……它通过使用固定点组合器绕过lambda限制。)
(=<<) :: Monad m => (a -> m b) -> m a -> m b
和(<=<) :: (b -> m c) -> (a -> m b) -> (a -> m c)
。 - rampionApplicative
版本:(<*>) :: Applicative m => m (a -> b) -> m a -> m b
和liftA2 (.) :: Applicative m => m (b -> c) -> m (a -> b) -> m (a -> c)
。 - rampionm a
。这个值必须由某个装饰函数(可能是return
)创建。因此,绑定实际上将创建该值的函数与另一个函数(续集)组合起来。应用程序也可以用于组合,但它也可以作用于文字。当然,文字可以被替换为一个取单位的lambda,然后应用程序就变成了简化的组合。 - Bartosz Milewski