home search about rss feed twitter ko-fi

for-Expressions Are Better Than getOrElse (Scala 3 Video)

A great thing about using for expressions that might not be immediately obvious is that they’re better than using a series of getOrElse calls. I’ll demonstrate what I mean in this lesson.

The for expression “Happy Path”

Imagine asking a user to input four Int values as strings. You could then sum those values using this for expression:

val sum = for {
    a <- makeInt(input1)
    b <- makeInt(input2)
    c <- makeInt(input3)
    d <- makeInt(input4)
} yield a + b + c + d

Next, assume that they gave you exactly what you asked for, four strings that properly convert to integers, like this:

val sum = for {
    a <- makeInt("1")
    b <- makeInt("2")
    c <- makeInt("3")
    d <- makeInt("4")
} yield a + b + c + d

This specific example shows the Happy Path for this for expression. When I say, “Happy Path,” it means that you got the input values you desired, and therefore the for expression completes properly, with sum ending up as Option(10).

The for expression “Unhappy Path”

But a great thing about the for expression is that it also handles the “Unhappy Path,” i.e., the path that is followed when one or more of the String values don’t convert to Int values:

val sum = for {
    a <- makeInt("ka-boom!")
    b <- makeInt("2")
    c <- makeInt("3")
    d <- makeInt("4")
} yield a + b + c + d

Can you guess what happens with this code?

Does it throw an exception? No, of course not, pure functions don’t throw exceptions!

Does sum end up with a value? Yes, it does. sum is bound to None, as the REPL shows:

scala> val sum = for {
     |     a <- makeInt("ka-boom!")
     |     b <- makeInt("2")
     |     c <- makeInt("3")
     |     d <- makeInt("4")
     | } yield a + b + c + d
sum: Option[Int] = None

What happens is that the for expression short-circuits when this line of code is reached:

a <- makeInt("ka-boom!")

When makeInt receives a String it can’t transform into an Int, these things happen:

  • makeInt returns a None
  • The for expression short-circuits at that point
  • sum is bound to None

The None result tells you that makeInt was given something it couldn’t convert to an Int.

It’s important to note that even though this is an Unhappy Path, the good news is that the bad input doesn’t completely blow up your for expression; the expression simply yields None (which is much better than doing something like throwing an exception).

Compare that to getOrElse

Compare that code to the only thing you can do with getOrElse:

val sum = makeInt("ka-boom!").getOrElse(0)
        + makeInt("2").getOrElse(0)
        + makeInt("3").getOrElse(0)
        + makeInt("4").getOrElse(0)

When you paste that code into the REPL, you’ll see that sum is assigned to 9 — a completely different result!

If you think that’s bad, this example just uses Option[Int] values; imagine what could happen when you use complex, real world classes like Option[Person], Option[Pizza], or Option[List[List[Friend]]].

This is one reason why experienced Scala/FP developers tell you not to use getOrElse.

Another good thing about using for expressions that might not be immediately obvious is that they’re better than using a series of getOrElse calls. I’ll demonstrate what I mean in this lesson.

The for expression “Happy Path”

Imagine asking a user to input four Int values as strings. You could then sum those values using this for expression:

val sum = for {
    a <- makeInt(input1)
    b <- makeInt(input2)
    c <- makeInt(input3)
    d <- makeInt(input4)
} yield a + b + c + d

Next, assume that they gave you exactly what you asked for, four strings that properly convert to integers, like this:

val sum = for {
    a <- makeInt("1")
    b <- makeInt("2")
    c <- makeInt("3")
    d <- makeInt("4")
} yield a + b + c + d

This specific example shows the Happy Path for this for expression. When I say, “Happy Path,” it means that you got the input values you desired, and therefore the for expression completes properly, with sum ending up as Option(10).

The for expression “Unhappy Path”

But a great thing about the for expression is that it also handles the “Unhappy Path,” i.e., the path that is followed when one or more of the String values don’t convert to Int values:

val sum = for {
    a <- makeInt("ka-boom!")
    b <- makeInt("2")
    c <- makeInt("3")
    d <- makeInt("4")
} yield a + b + c + d

Can you guess what happens with this code?

Does it throw an exception? No, of course not, pure functions don’t throw exceptions!

Does sum end up with a value? Yes, it does. sum is bound to None, as the REPL shows:

scala> val sum = for {
     |     a <- makeInt("ka-boom!")
     |     b <- makeInt("2")
     |     c <- makeInt("3")
     |     d <- makeInt("4")
     | } yield a + b + c + d
sum: Option[Int] = None

What happens is that the for expression short-circuits when this line of code is reached:

a <- makeInt("ka-boom!")

When makeInt receives a String it can’t transform into an Int, these things happen:

  • makeInt returns a None
  • The for expression short-circuits at that point
  • sum is bound to None

The None result tells you that makeInt was given something it couldn’t convert to an Int.

It’s important to note that even though this is an Unhappy Path, the good news is that the bad input doesn’t completely blow up your for expression; the expression simply yields None (which is much better than doing something like throwing an exception).

Compare that to getOrElse

Compare that code to the only thing you can do with getOrElse:

val sum = makeInt("ka-boom!").getOrElse(0)
        + makeInt("2").getOrElse(0)
        + makeInt("3").getOrElse(0)
        + makeInt("4").getOrElse(0)

When you paste that code into the REPL, you’ll see that sum is assigned to 9 — a completely different result!

If you think that’s bad, this example just uses Option[Int] values; imagine what could happen when you use complex, real world classes like Option[Person], Option[Pizza], or Option[List[List[Friend]]].

This is why experienced Scala/FP developers tell you not to use getOrElse. When you first start working with Scala it seems like getOrElse could be a good thing, but (a) it can also be an incorrect thing when used in the wrong places, and (b) in the long run it can keep you from seeing a much better solution in the for expression.

(Embrace the idioms!)

Summary

In the last two lessons I showed a for expression like this:

val sum = for {
    a <- makeInt(string1)
    b <- makeInt(string2)
    c <- makeInt(string3)
    d <- makeInt(string4)
} yield a + b + c + d

I mentioned that the “Happy Path” is when all of the String values can be converted to Int values, and the “Unhappy Path” is what happens when a String value can’t be converted to an Int.

Also, even though that code can be said to have Happy and Unhappy paths, the great thing about the for expression is that it handles both cases. In the Happy case sum is bound to an Option[Int], and in the Unhappy case sum is bound to None.

In this lesson I also demonstrated that this code is better than a getOrElse approach because getOrElse can give you a wrong (or inappropriate) answer. Beyond that, as you’ll see in the rest of this book, it can also keep you from embracing the Scala idioms that will lead you to a new style of coding.

Update: All of my new videos are now on
LearnScala.dev