1. Introduction

Often in our code, we deal with failures by throwing exceptions. However, exceptions aren’t a good way to report a failure, especially in Scala. This is because exceptions in Scala aren’t part of a function’s signature, and the compiler doesn’t provide much help tracking them.

A much better alternative is the Either[A, B] type. In this tutorial, we’ll see the Either type in Scala and how we can use it. Lastly, we’ll briefly compare it with another popular Scala type, Option[A].

2. The Either Type

The Either[A, B] type represents a “wrapper” that can contain either a value of type A or type B.

*More formally, Either models a disjoint union, and an instance of it can be either Left[A] or Right[B].* Conventionally, we use the left projection (Left[A]) to model an error, whereas we use the right one (Right[B]) to hold a value produced by a successful computation. As a side note, when the left projection contains an exception type we can also use Try[T].

Since Scala 2.12, Either is right-biased. This means that, by default, many class methods operate on the right projection.

3. Common Operations With Either

The most common operation when working with Either is probably extracting the underlying value. Let’s see the most basic way, using pattern matching:

val e: Either[String, Int] = Right(5)
e match {
  case Right(i) => assert(i == 5)
  case Left(_) => fail("The Either should be a Right")
}

Pattern matching specifies explicitly what to do with both the left and the right projections of Either. This is rather cumbersome if we want to get to the right projection.

If both branches of the Either are of the same type, we can use Either::merge to extract the value, regardless of whether it’s Left or Right:

val e: Either[Int, Int] = Right(5)

assert(e.merge == 5)

We can use Either::merge even if the two projections aren’t of the same type. Nonetheless, in that case, the type of the returned value will be the minimum supertype of the types of the two projections. For example, calling Either::merge on an instance of Either[String, Int] returns Any.

A more straightforward way is using Either::getOrElse:

val e: Either[String, Int] = Left("Some error")
val defaultValue = 10

assert(e.getOrElse(defaultValue) == 10)

The e instance contains a Left[String] in the example above. Therefore, when we attempt to fetch its right projection, we get the default value, 10, in this case. We can achieve the same result using Either::fold:

val e: Either[String, Int] = Left("Some error")
val defaultValue = 10

assert(e.fold(_ => defaultValue, identity) == 10)

Either::fold lets us specify two functions mapping, respectively, the left and the right projections. In the example above, the function working on the left projection returns a default value, discarding the String contained in the Left[String] instance. On the other hand, the function working on the right projection is simply the identity function, returning the value of the projection as-is.

Either::fold is very powerful. Using the same idea as before, we can use it to map only one of the branches of the Either. Let’s see how to map the left projection:

case class MyError(msg: String)

val e: Either[String, Int] = Left("Some error")

assert(e.fold(error => Left(MyError(error)), identity) == Left(MyError("Some error")))

In the example above, we used Either::fold to map the left projection of Either to instantiate a custom class with the error message. We could use the same solution to map the right projection. However, since Either is right-biased, we can rely on Either::map for that:

val e: Either[String, Int] = Right(5)

assert(e.map(_ + 10) == Right(15))

4. Similarities With the Option Type

We can see Either[A, B] as an alternative to Option[A]. In particular, Option[A] stores either a value of type A (Some[A]) or no value at all (None). However, Either holds more information than Option since we can use Left to store details on why the Right value is missing or could not be computed.

As such, we can see Either as a generalization of Option. For example, Option[Int] is more or less equivalent to Either[Unit, Int]. In this case, the left projection would be a value of type Unit, which carries little information and is similar to None. On the other hand, the right projection would be a value of type Int, similar to Some[Int].

Let’s see how we can turn an Option instance into an Either instance:

val opt: Option[Int] = Option(10)
val either: Either[Unit, Int] = opt.toRight(())
    
assert(either.isRight)

In the example above, we create an instance of Option and then use the toRight() method to turn it into an instance of Either[Unit, Int]. It’s worth noting that the argument to toRight() is (), a value of type Unit.

Similarly, we can turn instances of Either into instances of Option:

assert(Right(10).toOption.isDefined)
assert(Left(10).toOption.isEmpty)

As we saw above, conventionally, the right projection represents a successful computation. Therefore, the Either::toOption method turns it into an instance of Some. On the contrary, the left projection is mapped to None.

5. Conclusion

In this article, we looked at the Either[A, B] type in Scala. First, we saw what the type represents and how to use it. Then, we saw some common operations on it, such as retrieving the left or right projections or mapping those projections to some other types. Then, we analyzed its similarities and differences with the Option[A] type.

As usual, the source code for the examples can be found over on GitHub.


« 上一篇: Scala 中的 locally 块
» 下一篇: Spire 简介