You just need to move where the case
occurs:
p <- case thing of
True -> func
False -> otherFunc
return $ Record {program=p, otherFields=oF}
But this is assuming that func
and otherFunc
have the same type, namely IO Program
, where Program
is whatever type p
is.
You have to do it this way because the syntax is defined (more or less) as
case <expr> of
<pattern1> -> <expr1>
<pattern2> -> <expr2>
...
<patternN> -> <exprN>
However, the syntax <pattern> <- <expr>
is not in itself an expression, you can't just bind a name using the <-
syntax to get a complete expression, you have to do something with it afterwards. Think of expressions as something that return a value, any value at all. The <-
syntax does not have a return value, it's extracting a value from a monadic context and assigning it to a name. This is similar to how let x = y
doesn't have a return value, it's just binding a value to a name. x
and y
themselves have return values, but let x = y
does not.
This is why you can't just put p <- func
by itself in a case branch, it would have to have other things with it. However, if your overall case expression has a return value like IO Program
, then you can extract that using <-
.
Pretty much all of this goes for let
bindings as well. If instead your func
and otherFunc
functions have the type Program
, instead of IO Program
, then you could just do
let p = case thing of
True -> func
False -> otherFunc
in Record {program=p, otherFields=oF}
Or more succinctly
Record {program=(if thing then func else otherFunc), otherFields=oF}
You can use if-then-else
with monadic bind as well:
do
p <- if thing then func else otherFunc
return $ Record {program=p, otherFields=oF}