JIT: IO, Option, Try, Either, and for-Expressions

Option, Try, and Either don’t mix in for-expressions

A second important thing to know at this point is that you CANNOT mix types like Option, Try, and Either in for expressions (not easily, anyway).

To demonstrate this, imagine that your code base has two functions like this, one that returns an Option and another that returns a Try:

import scala.util.{Try, Success, Failure}
import scala.util.control.Exception.allCatch
import io.StdIn

def makeInt(s: String): Option[Int] =
    allCatch.opt(s.toInt)

def makeDouble(s: String): Try[Double] =
    Try(s.toDouble)

and then you find yourself in a situation where you want to prompt a user to enter an Int and then a Double:

print("Enter an Int: ")
val intAsString = StdIn.readLine()

print("Enter a Double: ")
val doubleAsString = StdIn.readLine()

val maybeInt: Option[Int] = makeInt(intAsString)
val maybeDouble: Try[Double] = makeDouble(doubleAsString)

So far, so good. But now, let’s say that you want to use them in sequence in a for expression, to add them together:

for
    i <- maybeInt
    d <- makeDouble
yield
    i + d

Sadly, this code won’t compile. The types Opt[Int] and Try[Double] just won’t mix.

There are ways to get around this in a small example like this, but in the more-complex real world, this is a huge problem.

I explain why these don’t mix in Functional Programming, Simplified.

The solution

The solution is to use an IO data type whenever you communicate with the outside world. When you do that, you can use as many IO values in a for expression as you want. IOs are built to handle all sorts of side effects, and they will all work happily together when you sequentially compose them in for expressions. (Life is good with IO.)

Now I’ll add an oversimplification here: This is where you need to choose a library like ZIO OR Cats Effect. There are ways to get their IO types to work together, but in general you want to choose one OR the other.

As a personal statement, I’m just a teacher and a writer, so I don’t want to get into suggesting which library you should use. That’s beyond the scope of this book, and you can search for terms like “ZIO vs Cats Effect” to find the pros and cons of each library.

That being said, I’ll only demonstrate ZIO in this book, because I want to avoid the confusion of switching back and forth between the two.

Key point

Getting back to this lesson, the key point is that types like Option, Try, and Either don’t mix well: it’s hard to combine their results in for expressions, as well as other places.

As shown in this book, the goal of these types is to handle errors in a functional way, and we generally use them when dealing with side effects, such as communicating with the outside world. But when you can’t later combine them — what FPers call composing values, or composition — this is a big problem.

Therefore, IO types have been created as a way to deal with I/O and other side effects. They have all the benefits of Try and Either, but:

  • They’re lazy, so we keep the algebraic substitution model
  • They can be combined in for expressions
  • They have MANY more features than these error-handling types

In the previous lesson and this lesson I covered the first two bullet points, and in the next lesson we’ll start looking at some of the features that industrial-strength IO types have in the ZIO and Cats Effect libraries.