A Story of Left and Right
Perhaps you’ve been using Scala for years, or perhaps you’ve never heard of it. In this article, I want to tell you the story of Either[+A, +B], and its subclasses Left and Right.
Intuitively, Either is an ADT (Algebraic Data Type) representing xor, or exclusive disjunction, which essentially means the value is either Left or Right, but never both. As with most ADTs in Scala, Either inherits from Scala’s Product trait.
Either is what we call a higher-kinded type, that is, it takes types to construct a new type. According to its signature Either[+A, +B], it takes two covariant types. Within the Scala community, it’s common practice to parameterize error types on the left and value types on the right, akin to Try, Failure, and Success, but without an error necessarily containing a throwable exception.
One advantage of encoding errors within Either types is that the error types are encoded within the method or object signature. Another common use case is to prevent the instantiation of an invalid state, which we’ll get into later.
What’s a Monad anyway?
Either is what we call in the FP community, a monad. This essentially means that dependent Either’s can be chained together in computation to return an Either.
In order to do so, we utilize the flatMap method or its alias >>= in libraries like cats. Let’s take a look:
If we were to map Either’s with functions that return Either’s, they would quickly nest within one another:
scala> Right(1) map (num => Right(num + 2))
res15: scala.util.Either[Nothing,scala.util.Right[Nothing,Int]] = Right(Right(3))
If we wanted to map it again, it would become unwieldy:
scala> Right(1) map (num => Right(num + 2)) map (_.map(num => Right(num * 3)))
res16: scala.util.Either[Nothing,scala.util.Either[Nothing,scala.util.Right[Nothing,Int]]] = Right(Right(Right(9)))
Obviously, there must be a better solution, which is flatMap:
scala> Right(1) flatMap (value => Right(value + 2)) flatMap (value => Right(value * 3))
res17: scala.util.Either[Nothing,Int] = Right(9)
Within Scala, there is also syntactic sugar for composing with flatMap. Introducing, the for-comprehension:
领英推荐
scala> for {
| num1 <- Right [Nothing, Int] (1).right
| num2 <- Right [Nothing, Int] (num1 + 1).right
| num3 <- Right [Nothing, Int] (num2 * 3).right
| } yield num3
res18: scala.util.Either[Nothing,Int] = Right(6)
Perhaps you have an idea as to why this provides such powerful error-handling capabilities. If any comprehension is a Left, then the yield will return the first Left:
scala> for {
| num1 <- Right [String, Int] (1).right
| num2 <- Right [String, Int] (num1 + 1).right
| ohNoAnError <- Left [String, Int] ("Something tragic and horrible happened").left
| } yield num2
res19: scala.util.Either[Any,Int] = Left(2)
Whoa! Why does this work? There’s something I left out, which is that Either types are right-biased. That means that whenever you map, it maps the right value. When you get a Left, you can keep mapping, while that operation works on a right, so it doesn’t change that Left value.
Projections? What are those?
You may have noticed in the example above, that I called the .right or .left function on a Right or Left value. These methods create what we call projections. Projections on Either values allow us to modify the bias. If we call .left, we change the bias to the Left side. It allows us to map the Left side.
I WANT MY VALUES!!!
So, you want your values. What can you do? It’s common practice to use match expression to extract from Left and Right:
Right(10) match {
case Right(value) => // Do something with value
case Left(err) => // Do something with err
}
And, so we have it: your values. Now you know how to use Either. Now, go do something useful.
Regards,
Bradly O.
Functional Scala Programmer
2 年Make sure to follow The Scala Developer.