Short answer: Use let without in in the body of a do-block, and in the part after the | in a list comprehension. Anywhere else, use let ... in ....
The keyword let is used in three ways in Haskell.
-
The first form is a let-expression.
let variable = expression in expressionThis can be used wherever an expression is allowed, e.g.
> (let x = 2 in x*2) + 3 7 -
The second is a let-statement. This form is only used inside of do-notation, and does not use
in.do statements let variable = expression statements -
The third is similar to number 2 and is used inside of list comprehensions. Again, no
in.> [(x, y) | x <- [1..3], let y = 2*x] [(1,2),(2,4),(3,6)]This form binds a variable which is in scope in subsequent generators and in the expression before the
|.
The reason for your confusion here is that expressions (of the correct type) can be used as statements within a do-block, and let .. in .. is just an expression.
Because of the indentation rules of haskell, a line indented further than the previous one means it’s a continuation of the previous line, so this
do let x = 42 in
foo
gets parsed as
do (let x = 42 in foo)
Without indentation, you get a parse error:
do (let x = 42 in)
foo
In conclusion, never use in in a list comprehension or a do-block. It is unneccesary and confusing, as those constructs already have their own form of let.