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,函数对整个 thing 的 f
组件没有影响。他们严格在内部工作,不能影响 "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"。插图:
在我读到的许多文章中,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,函数对整个 thing 的 f
组件没有影响。他们严格在内部工作,不能影响 "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"。插图: