我不明白什么时候必须使用let
,什么时候必须使用<-
绑定。
3 回答
let
为函数调用的结果命名。
<-
将当前 monad 中的 monadic 操作的结果绑定到一个名称。
他们完全不同。用于let
monad 之外的函数的结果,即普通的纯函数。用于<-
任何单子,因为它“解包”单子结果并让您获得其中的值。
例如:
假设具有以下签名的 IO 函数
frobnicate :: String -> IO Bool
和一个纯函数
dothing :: Bool -> Bool
我们做得到
main :: IO ()
main = do
x <- frobnicate "Hello"
let y = frobnicate "Hello"
-- z <- dothing x
let z = dothing x
return ()
我们知道,x :: Bool
因为Bool
已经IO
为我们从操作结果中提取了 (操作运行,结果被调用x
,所以我们以后可以使用它)。
我们也知道y :: IO Bool
- 操作尚未运行,它是未来 IO 操作的潜力。所以我们唯一能做的事情y
就是稍后运行它,绑定结果并以Bool
这种方式进入内部,但在a之后let
甚至Bool
还不存在。
第三行被注释掉,因为它不会编译 - 你不能对不在相关 monad 中的操作执行 monadic 绑定。dothing
不返回IO
任何内容,因此您不能将其绑定在IO ()
函数中。
第四行很简单 -z
是 的结果dothing x
,其中x
的值是从frobnicate "Hello"
较早运行中解包出来的。
所有这些只是下面“真正的” monad 操作的语法糖,因此可以扩展(没有注释掉的部分)类似于
main = frobnicate "Hello" >>= (\x -> let y = frobnicate "Hello"
z = dothing x
in return ())
这个例子当然毫无意义,但希望它能说明符号的不同之处let
和<-
不同之处。do
TL;DR:<-
用于为一元操作的结果let
命名,为其他一切命名。
<-
is to >>=
( bind
) where let
is tofmap
在一个do
块中。
从这里偷一个例子:
do x1 <- action1 x0
x2 <- action2 x1
action3 x1 x2
-- is equivalent to:
action1 x0 >>= \ x1 -> action2 x1 >>= \ x2 -> action3 x1 x2
action1
, action2
&action3
都返回某种单子,比如:
action1 :: (Monad m) => a -> m b
action2 :: (Monad m) => b -> m c
action3 :: (Monad m) => b -> c -> m d
您可以这样重写 let 绑定:
-- assume action1 & action3 are the same
-- but action2 is thus:
action2 :: b -> c
do
x1 <- action1 x0
let x2 = action2 x1
action3 x1 x2
do
x1 <- action1 x0
x2 <- return & action2 x1
action3 x1 x2
-- of course it doesn't make sense to call action2
-- an action anymore, it's just a pure function.
一个很好的例子来可视化做什么<-
:
do
a <- ['a'..'z']
b <- [1..3]
pure (a,b)
您可以在try.frege-lang.org的在线 REPL 中尝试此操作 (您可以将其输入为单行:
do { a <- ['a'..'z']; b <- [1..3]; pure (a,b) }