3

我不明白什么时候必须使用let,什么时候必须使用<-绑定。

4

3 回答 3

5

let为函数调用的结果命名。

<-将当前 monad 中的 monadic 操作的结果绑定到一个名称。

他们完全不同。用于letmonad 之外的函数的结果,即普通的纯函数。用于<-任何单子,因为它“解包”单子结果并让您获得其中的值。

例如:

假设具有以下签名的 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命名,为其他一切命名。

于 2016-02-29T16:32:00.257 回答
3

<-is to >>=( bind) where letis 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.
于 2016-02-29T15:15:21.663 回答
1

一个很好的例子来可视化做什么<-

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) }
于 2016-02-29T19:18:55.027 回答