If else in for comprehension when calculating monadic expression

I can’t find this anywhere. In Haskell we can write a monadic expression like this:

fun  = do
  if 1 > 0 then do
      return 1
  else do
      return 0

I am not sure if I can do sth that concise in Scala with for comprehension. Or maybe I can write it the other way without explicit if-else? Everything seems fine when I am calculating things in a single line like this:

for {
    l1 <- calc1()
    l2 <- calc2()
    l3 <- calc3()
 } yield (l1 + l2  + l3)

but what to do when I want to use if-else inside? Is it a sign that I am doing sth wrong?

Do you just mean

...
yield (if (l1 > l2) l1 + l3 else l2 + l3)

?

(You can make it a block, too: yield { ... }. You can always turn an expression into a block whose last statement is an expression. But if-statements are expressions already.)

Should I keep my monadic expressions short? I thought about sth like this when if and else are between calculated value (with <-)

fun  = do
if 1 > 0 then do
  if ... then do
      x1 <- f1()
      return x1
   else 
      x2 <- f2()
      return x2
else do
  x3 <- f3()
  return x3

is Scala for comprehension as expressive as Haskell do?

In general they are both syntax sugar for monadic bind/flatMap and fmap/map functions, so they should be equivalent in expressiveness.

The last example you posted wouldn’t work in either language. Keep in mind that return in Haskell has nothing to do with return in imperative languages like Python/Java, etc… In particular you cannot use it as a statement to return early from a computation. return is an alias for pure, which takes a value and lifts it into the monad you’re using. So if the do-notation is used with Maybe, return will lift 1 into Maybe, so it evaluates to Just 1.

The very first example you posted has redundant usage of do and return.

You’d normally write it like this (and probably replace return with pure, which is the same but a clearer name):

fun :: Maybe Int
fun  = 
  if 1 > 0 then
    return 1
  else
    return 0

Using a conditional in for-comprehensions isn’t any different from using one anywhere else in Scala. You just have to follow the types and understand that if-else is an expression, not a statement.

def calc1(): Option[Int]
def calc2(): Option[Int]
def calc3(): Option[Int]

for {
    intA <- calc1()

    intB <- if (intA > 0) calc2() else calc3()
} yield intA + intB

Note that the if-else-expression has type Option[Int], so we unwrap it the same way we did with the result of calc1().

edit: The Haskell do-notation is confusing for beginners because the last expression needs to have the same type as the Monad it operates on. But the often needed lifting into the Monad isn’t automatic, so people use return to do this. But return is just an alias for pure and confuses people coming from imperative languages, where return has special meaning.

Learn You A Haskell introduces the do-notation without return or pure and uses the specific constructor of the Monad (for Maybe this is Just). Imo that’s the best way to go about it:

foo :: Maybe String  
foo = do  
    x <- Just 3  
    y <- Just "!"  
    Just (show x ++ y)
3 Likes

Just to amend the other replies… A straightforward translation to Scala, assuming Option as the monadic type, might be

def fun: Option[Int] =
  for {
    res <-
      if(1 > 0)
        for { o <- Some(1) } yield o
      else
        for { z <- Some(0) } yield z
  } yield res

However, this feels excessively complicated in both languages. The original Haskell expression could be condensed to

pure $ if 1 > 0 then 1 else 0

…translating to Scala…

Some(if(1 > 0) 1 else 0)

Note that Scala doesn’t have any builtin notion of a monad, in particular of pure/return - it just has some “duck typing” that allows to use for expressions on types providing map/flatMap methods with the proper signature. If you want explicit abstractions for functor, monad, etc., you’ll have to pull in libraries like cats or scalaz.

3 Likes