home search about rss feed twitter ko-fi

FEH: Either (Scala 3 Video)

Introduction: Using Either instead of Option

The last error-handling data type that’s built into Scala is named Either. Like Option and Try, Either has two sub-types named Left and Right, and they match up like this:

Option  <=>  Try      <=>  Either
Some    <=>  Success  <=>  Right
None    <=>  Failure  <=>  Left

That’s a slight over-simplification, because what happens with Either is that Right and Left can contain anything. By convention, Right holds the success value and Left holds the error value, but technically you can use those two values however you like.

makeInt with Either

That being said, here’s the makeInt function written to use Either. In this case I’ll return a String in the Left position:

// no 'import' statements are needed for this

/**
 * Left is a String.
 */
def makeInt(s: String): Either[String, Int] = 
    try
        Right(s.toInt)
    catch
        // for this example i convert the exception to a string
        case e: NumberFormatException => Left(e.getMessage)

Now when you call makeInt with good and bad strings, you’ll see these results:

// success case
makeInt("1")      // Right(1)

// failure case
makeInt("one")    // Left(For input string: "one")

In that example I return a String in the Left position, but I can also return an exception (or any other data type):

/**
 * The Left type is now an Exception.
 */
def makeInt(s: String): Either[Exception, Int] = 
    try
        Right(s.toInt)
    catch
        case e: NumberFormatException => Left(e)

This function’s error value looks like this:

// failure case
makeInt("one")   //Left(java.lang.NumberFormatException: For input string: "a")

Handling the result

Common ways to work with the result of an Either are match and for expressions.

The second version of makeInt returns an exception in the Left position, and this is how that function works in a match expression:

makeInt(aString) match
    case Right(i) => println(s"Success: i = $i")
    case Left(e)  => println(s"Failed, exception = $e")

And this is what that function looks like when its used with multiple strings that can be converted to integers in a for expression:

val maybeSum: Either[Exception, Int] =
    for
        i <- makeInt("1")
        j <- makeInt("2")
        k <- makeInt("3")
    yield
        i + j + k

// result:  maybeSum == Right(6)

As shown in the comment, in this situation, maybeSum has the value Right(6). And this code shows what the Unhappy Path looks like when one or more strings can’t be converted to integers:

val maybeSum: Either[Exception, Int] =
    for
        i <- makeInt("1")
        j <- makeInt("Hi mom!")
        k <- makeInt("3")
    yield
        i + j + k

// result:
maybeSum == Left(java.lang.NumberFormatException: For input string: "Hi mom!")

Here, maybeSum is a Left, and it contains that NumberFormatException.

And just like Option and Try, whether you get a Right or Left back from the for expression, you can handle its result in a match expression:

maybeSum match
    case Right(i) => println(s"Success: i = $i")
    case Left(e)  => println(s"Failed: msg = ${e.getMessage}")

Notice that when you use Either, both Right and Left are containers that contain the success value and error value, respectively.

Reading an Either signature

In the second version of the makeInt function, the return value Either[Exception, Int] can read as, “If the computation succeeds, I’ll get an Int back that’s wrapped in the Right. If it fails, I’ll get the exception in the Left.” The declaration of the two possible return types makes Either more flexible than the Option and Try types.

Two technical points

There are at least two interesting technical points to know about the Either data type.

First, as that Scaladoc page notes, Either represents a value that is one of two possible types, and this is technically known as a disjoint union.

Second, Either is right-biased. The Scaladoc states, “this means that Right is assumed to be the default case to operate on.” When you use Either in match and for expressions as I showed, this doesn’t matter. But when you want to work directly with maybeSum, such as calling a map method on it, that’s where this is important.

The key is that when you call map on maybeSum and it is a Left, map returns the Left value unchanged, as shown in the REPL:

// when maybeSum is a Left:
scala> maybeSum.map(_ * 10)
val res0: Either[Exception, Int] =
    Left(java.lang.NumberFormatException: For input string: "Hi mom!")

Conversely, when maybeSum is a Right, map and other methods work as desired:

// when maybeSum is a Right:
scala> maybeSum.map(_ * 10)
val res1: Either[Exception, Int] = Right(60)

Pure functions cannot lie

Once again I’ll reiterate this point about the type signatures of pure functions: they cannot lie. As shown here, the makeInt function screams loud and clear, “I’ll give you an Int in the Right position if everything goes well, but if something goes wrong, you’ll get an Exception in the Left”:

def makeInt(s: String): Either[Exception, Int]
                        ----------------------

Summary: Whenever you see Either, Try, or Option as the return type of a function, you know that something can go wrong inside that function.

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