brooks94
brooks94

Reputation: 3936

What's with the 'in' keyword?

In Haskell, why do you not use 'in' with 'let' inside of a do-block, but you must otherwise?

For example, in the somewhat contrived examples below:

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)

It's an easy enough rule to remember, but I just don't understand the reason for it.

Upvotes: 15

Views: 5084

Answers (3)

Luis Casillas
Luis Casillas

Reputation: 30237

The short answer is that Haskell do blocks are funny. Haskell is an expression-based language—except in do blocks, because the point of do blocks is to provide for a "statement" syntax of sorts. Most "statements" are just expressions of type Monad m => m a, but there are two syntaxes that don't correspond to anything else in the language:

  1. Binding the result of an action with <-: x <- action is a "statement" but not an expression. This syntax requires x :: a and action :: Monad m => m a.
  2. The in-less variant of let, which is like an assignment statement (but for pure code on the right hand side). In let x = expr, it must be the case that x :: a and expr :: a.

Note that just like uses of <- can be desugared (in that case, into >>= and lambda), the in-less let can always be desugared into the regular let ... in ...:

do { let x = blah; ... }
    => let x = blah in do { ... }

Upvotes: 6

macron
macron

Reputation: 1836

You are providing expressions to define both afunc and amfunc. Let-expressions and do-blocks are both expressions. However, while a let-expression introduces a new binding that scopes around the expression given after the 'in' keyword, a do-block isn't made of expressions: it is a sequence of statements. There are three forms of statements in a do-block:

  1. a computation whose result is bound to some variable x, as in

    x <- getChar
    
  2. a computation whose result is ignored, as in

    putStrLn "hello"
    
  3. A let-statement, as in

    let x = 3 + 5
    

A let-statement introduces a new binding, just as let-expressions do. The scope of this new binding extends over all the remaining statements in the do-block.

In short, what comes after the 'in' in a let-expression is an expression, whereas what comes after a let expression is a sequence of statements. I can of course express a computation of a particular statement using a let-expression, but then the scope of the binding would not extend beyond that statement to statements that follow. Consider:

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

The above code causes the following error message in GHC:

Not in scope: `x'

whereas

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

works fine.

Upvotes: 15

Sarah
Sarah

Reputation: 6696

You can indeed use let .. in in do-notation. In fact, according to the Haskell Report, the following

do{let decls; stmts}

desugars into

let decls in do {stmts}

I imagine that it is useful because you might otherwise have to have some deep indentation or delimiting of the "in"-block, going from your in .. to the very end of the do-block.

Upvotes: 9

Related Questions