假设我们有 g :: a -> b
,和f :: b -> c
。我们可以这样写:
-
f . g :: a -> c
.
如果我们的函数返回一元值(即上下文中的值),例如 g1 :: (Monad m) => a -> m b
和f1 :: (Monad m) => b -> m c
。我们可以这样写:
-
f1 <=< g1 :: (Monad m) => a -> m c
. -
return x >>= g1 >>= f1
,其中x :: a
,以获得一个值。甚至是 lambda\x -> return x >>= g1 >>= f1
.
看来<=<
与 .
更平行在语法方面。 <=<
更容易理解Monad
只是关于保留上下文的函数组合。为什么是>>=
比<=<
更常被谈论?
最佳答案
<=<
是解释 monad laws 的好方法。 :
f <=< return = f -- right identity
return <=< g = g -- left identity
f <=< (g <=< h) = (f <=< g) <=< h -- associativity
它对于演示 Kleisli 箭头的类别非常有用:
newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }
instance Monad m => Category (Kleisli m) where
Kleisli f . Kleisli g = Kleisli (f <=< g)
id = Kleisli return
您会看到它出现在无积分程序中。就我个人而言,我也很喜欢它的同行=<<
.
虽然它使谈论单子(monad)定律和组合变得更容易,但我认为仍然有一些强有力的说教原因 >>=
在 monad 教程和 Haskell 介绍中是首选。
第一个原因是<=<
的强项是无点代码,对于来自 C 语法系列语言(C、C++、Java、Python 等)的人来说,大多数情况下,无点代码一开始更难理解。
如果“point-free”对您来说是一个陌生的形容词,那么这里有同一函数的三个实现:
f a b = a + b * 2
f a = (a +) . (* 2)
f = flip (.) (*2) . (+)
它们都运行相同的计算,但最后一个采用所谓的“无点”风格,其中左侧的变量已通过 eta conversion 删除。 。
这个例子很像一个稻草人,但无点风格很诱人,很容易导致初学者很难理解的代码。
另一个原因是初学者几乎都会问的问题之一是
“如何打开 IO String
以获得 String
?”当第一次遇到
haskell 的IO
单子(monad)。答案当然是,“你不这样做,你把其余的链起来
>>=
的计算", >>=
更容易解释这种关系
之间
putStrLn "Your first name: " >>= \_ ->
getLine >>= \first ->
putStrLn "Your last name: " >>= \_ ->
getLine >>= \last ->
putStrLn ("Hello " ++ first ++ " " ++ last)
和
do
putStrLn "Your first name: "
first <- getLine
putStrLn "Your last name: "
last <- getLine
putStrLn ("Hello " ++ first ++ " " ++ last)
当然,最后一个原因是 >>=
位于 Monad
的定义中, 和
<=<
不是,这就是语言的定义方式。人们是
当以下情况时,更有可能谈论类型类成员而不是任意函数
向其他人传授有关类型类的知识,尤其是当老师
对于该主题本身来说相对较新(正如许多 monad 教程作者一样)。
关于haskell - 为什么>>=比<=<更常被讨论?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45513040/