15

在 Haskell 中,为什么你不在 do-block 中使用 'in' 和 'let',但你必须不然呢?

例如,在下面有些人为的例子中:

afunc :: Int -> Int
afunc a = 
       let x = 9 in
       a * x

amfunc :: IO Int -> IO Int
amfunc a = do
       let x = 9
       a' <- a
       return (a' * x)

这是一个很容易记住的规则,但我就是不明白它的原因。

4

3 回答 3

15

您正在提供表达式来定义afuncamfunc。let 表达式和 do 块都是表达式。然而,虽然 let-expression 引入了一个新的绑定,该绑定在 'in' 关键字之后给出的表达式的范围内,但 do-block 不是由表达式组成的:它是一个statements序列。do-block 中有三种形式的语句:

  1. 结果绑定到某个变量的计算x,如

    x <- getChar
    
  2. 结果被忽略的计算,如

    putStrLn "hello"
    
  3. 一个 let 语句,如

    let x = 3 + 5
    

let-statement 引入了一个新的绑定,就像 let-expressions 一样。这个新绑定的范围扩展到 do-block 中的所有剩余语句。

简而言之,let 表达式中“in”之后的是一个表达式,而 let 表达式之后的是一系列语句。我当然可以使用 let 表达式来表达对特定语句的计算,但是绑定的范围不会超出该语句而扩展到后面的语句。考虑:

do putStrLn "hello"
   let x = 3 + 5 in putStrLn "eight"
   putStrLn (show x)

上述代码在 GHC 中导致以下错误消息:

Not in scope: `x'

然而

do putStrLn "hello"
   let x = 3 + 5
   putStrLn "eight"
   putStrLn (show x)

工作正常。

于 2012-07-26T18:10:53.027 回答
9

您确实可以let .. in在 do-notation 中使用。事实上,根据 Haskell 报告,以下

做 {let decls; 语句}

脱糖成

让 decls 做 {stmts}

我认为它很有用,因为否则您可能必须对“in”块进行一些深度缩进或定界,从您的 in .. 到 do-block 的最后。

于 2012-07-26T17:54:03.263 回答
6

简短的回答是 Haskelldo块很有趣。Haskell 是一种基于表达式的语言——除了在do块中,因为do块的目的是提供一种“语句”语法。大多数“语句”只是 type 的表达式Monad m => m a,但有两种语法与语言中的其他任何东西都不对应:

  1. <-用:绑定一个动作的结果x <- action是一个“语句”,而不是一个表达式。此语法需要x :: aaction :: Monad m => m a
  2. in-less 变体let,类似于赋值语句(但用于右侧的纯代码)。在let x = expr中,必须是x :: aexpr :: a

请注意,就像使用 of<-可以脱糖(在这种情况下,进入>>=和 lambda)一样,in-lesslet总是可以脱糖到常规中let ... in ...

do { let x = blah; ... }
    => let x = blah in do { ... }
于 2012-07-26T20:04:03.057 回答