Reputation: 3936
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
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:
<-
: x <- action
is a "statement" but not an expression. This syntax requires x :: a
and action :: Monad m => m a
.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
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:
a computation whose result is bound to some variable x
, as in
x <- getChar
a computation whose result is ignored, as in
putStrLn "hello"
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
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