“in”关键字是什么意思?

15
在Haskell中,为什么你不能在do块内部使用'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)

这是一个容易记住的规则,但我不理解其原因。


抱歉,我标记它为[haskell],但我刚编辑了文本以使其更清晰。 - brooks94
1
没有深层次的原因,这就是事实。 - augustss
3
如果在 do 块中有多个 let 语句,并且每个 let 后面都有一个 in,那么为了能够正确执行,每个 in 后面都需要紧跟一个 do(如果后面还有其他语句的话)。这种情况下使用 where 就很方便。注意,where 只能引用上文已定义的变量。 - Daniel Fischer
3个回答

15
你提供了表达式来定义afuncamfunc。let表达式和do块都是表达式。但是,尽管let表达式引入了一个新的绑定,其作用域限于'in'关键字后面给出的表达式,但是do块不是由表达式组成的:它是一系列语句。在do块中有三种形式的语句:
  1. 一种计算,其结果绑定到某个变量x,如下所示:

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

    putStrLn "hello"
    
  3. let语句,例如

    let x = 3 + 5
    
  4. let语句引入了一个新的绑定,就像let表达式一样。这个新绑定的作用域延伸到do块中剩余的所有语句。
  5. 简而言之,在let表达式中'in'后面是一个表达式,而在let语句中'in'后面是一系列语句。当然,我可以使用let表达式来表达一条特定语句的计算,但这时绑定的作用域不会延伸到后续的语句中。请考虑以下例子:
  6. 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)
    

    运行良好。


9
您可以在do-notation中使用let .. in。实际上,根据Haskell报告,以下内容:

do{let decls; stmts}

会被转换为

let decls in do {stmts}

我想这很有用,因为否则您可能需要对"in"块进行深度缩进或限定范围,从您的in..一直到do-block的结尾。

6
简短的回答是Haskell的do块很有趣。Haskell是一种基于表达式的语言,除了在do块中,因为do块的目的是提供一种“语句”语法。大多数“语句”只是类型为Monad m => m a的表达式,但有两种语法与语言中的其他内容不对应:
  1. 使用<-绑定操作结果: x <- action是一个“语句”,但不是表达式。此语法要求x :: a和action :: Monad m => m a。
  2. 无in变体的let,类似于赋值语句(但用于右侧的纯代码)。在let x = expr中,必须满足x :: a和expr :: a。
请注意,就像使用<-的用途可以被解糖(在这种情况下,解糖为>>=和lambda),无in的let始终可以被解糖为常规的let ... in ...。
do { let x = blah; ... }
    => let x = blah in do { ... }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接