Since the OP says "I still have to think more on why your solution works" in a comment, I thought I would add some explanation as a complementary answer.
Haskell's if condition then x else y
syntax is actually not an analogue for the standard if/then/else statement seen in almost every imperative language. It is much more closely analogous to conditional expression syntax (seen in C as condition ? x : y
, or in Python as x if condition else y
). Once you remember that, everything else about it follows naturally.
if condition then x else y
in Haskell is an expression for a value, not a statement. x
and y
are not "things to do" based on whether condition
is true or not, but simply 2 different values; the whole if/then/else expression is a value that is either equivalent to x
or equivalent to y
(depending on the condition).
So with that in mind, lets take a look at the working version suggested by Willem Van Onsem:
do
n0 <- if condition then expr0 else expr0'
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
Here the if condition then expr0 else expr0'
is entirely on the right hand side of a single <-
statement. So it's an expression for a value, just like expr1
and expr2
are on the following lines. It doesn't say to either bind n0
from expr0
or bind n0
from expr0'
, it just is either expr0
or expr0'
. The <-
statement that contains the if/then/else is what says to bind n0
, and it binds it unconditionally from the single value computed by the whole if/then/else.
We can easily see this by the fact that we could declare a top-level variable equal to the if/then/else totally independently of the do block (assuming that condition
, expr0
, and expr0'
are globally available) and replace the if/then/else by a reference to this variable.
foo = do
n0 <- z
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
z = if condition then expr0 else expr0'
Here it's very clear that the if/then/else has nothing at all to do with the binding of n0
in the do block.
Let's compare that to the original non-working version:
do
if condition then
n0 <- expr0
else
n0 <- expr0'
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
This is using an if/then/else with statements as the then and else parts. Instead of just "being" one value or another, this is saying to "do" one thing or another. That's not how Haskell's if/then/else works. The whole if/then/else needs to be able to be understood as an expression for a single value.
Again this should be clear if we imagine trying to factor out the if/then/else into a separate declaration:
foo = do
z
n1 <- expr1
n2 <- expr2
return (T n0 n1 n2)
z = if condition then
n0 <- expr0
else
n0 <- expr0'
It should be clear that this doesn't make any sense. The then and else parts aren't independent value-expressions, they only make sense inside a do block. And they need to be inside the particular do block in foo
, so that n0
is bound for later use in return (T n0 n1 n2)
.
Since the statements of do blocks are transformed into expressions anyway, you might think that putting statements as the then/else parts of an if expression should work. However the transformation of do blocks into expressions "cuts across" statements, so this doesn't work. For example this:
do n <- expr
rest
is equivalent to this:
expr >>= (\n -> rest)
I won't get into a full technical explanation of that if you don't already understand it, but hopefully you can see that the n
ends up more closely connected to rest
than it does with the expr
it was bound from in the statement (rest
represents the entire remaining contents of the do block, in more complicated examples). There is no single expression that represents just the n <- expr
part, which you could put inside the then or else part of an if/then/else expression.
if ... then ... then
, notif ... then ... else
. – Woodhouseif isJust maybeVar then Just (f (fromJust maybeVar)) else Nothing
is much better spelledf <$> maybeVar
. – Grain