1

So I'm not sure if what I want is in fact a mutable variable, but it's something likened to it.

I essentially want to do this:

case thing of
    True -> p <- func
    False -> p <- otherFunc
return Record {program=p, otherFields=oF}

Any way I can do something like that? I've made my function return an IO Record, so tried just putting the return in the case statement, but my last line needs to return it, and it doesn't.

Gentatsu
  • 696
  • 1
  • 6
  • 26

2 Answers2

6

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}
bheklilr
  • 53,530
  • 6
  • 107
  • 163
3

Yup!

let p = case thing of
   ...
return ...

This is syntax sugar in do notation (itself syntax sugar) for

let p = case thing of
   ...
  in do
       ...
       return ...
dfeuer
  • 48,079
  • 5
  • 63
  • 167
  • Awesome, thanks! You answered first, so I'll make yours correct! Only a fraction, though! – Gentatsu Mar 27 '15 at 17:38
  • @Gentatsu, our answers are a bit different! Think about where each is appropriate. – dfeuer Mar 27 '15 at 17:39
  • At the core, they both say that you let a variable equal the result of a case statement, which is what I wanted, and yours made me realise that, so I feel as if you deserve it. – Gentatsu Mar 27 '15 at 17:40
  • @Gentatsu, bheklilr's is actually saying something a bit different/more interesting. You should think about it carefully. – dfeuer Mar 27 '15 at 17:41
  • About the return type being the same? Yeah, I thought it was implied though? Since I wouldn't be able to put it in my record otherwise. – Gentatsu Mar 27 '15 at 17:43
  • @Gentatsu I think the more important distinction is `<-` vs `=`. Are `func` and `otherFunc` pure or do they have to perform `IO` to get a value? Either way, both of our solutions reduce down to the same advice, move the case expression fully to the other side of the binding symbol. – bheklilr Mar 27 '15 at 17:47
  • They perform IO, so I'm using <-. Yours is correct in my context and is a fuller answer and more complete answer including a few other useful examples which also fit in to the same vein. I'll accept yours then, if it'll make you guys happy =P. – Gentatsu Mar 27 '15 at 17:53