【发布时间】:2016-02-29 12:23:48
【问题描述】:
我不明白什么时候必须使用let,什么时候必须使用<- 绑定。
【问题讨论】:
-
详细了解
let的实际含义以及<-的实际作用。而且,可能是关于do符号。
我不明白什么时候必须使用let,什么时候必须使用<- 绑定。
【问题讨论】:
let 的实际含义以及<- 的实际作用。而且,可能是关于 do 符号。
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,但在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 为其他所有内容命名。
【讨论】:
<- 是 >>= (bind) 其中 let 是 fmap 在 do 块中。
从here中窃取示例:
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) }
【讨论】: