A Story of Left and Right

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.

#scaladeveloper Scala Matters Scala Jobs EPFL

Bradly Ovitt

Functional Scala Programmer

2 年

Make sure to follow The Scala Developer.

回复

要查看或添加评论,请登录

Bradly Ovitt的更多文章

  • Concerns of Domain Driven Design

    Concerns of Domain Driven Design

    Let's talk about what problems domain driven design solves. Domain driven design is a methodology to model several…

  • The Actor Model & Reactive Systems

    The Actor Model & Reactive Systems

    Let's talk about the actor model and building reactive systems. The actor model is a programming paradigm for building…

    1 条评论
  • Concerns of Reactive Systems

    Concerns of Reactive Systems

    Let's talk about why we build reactive systems, as software engineers/developers. As professionals, we have to balance…

    1 条评论
  • Software Transactional Memory

    Software Transactional Memory

    Software transactional memory (STM) is a programming paradigm that allows multiple threads to share access to shared…

  • Understanding the Elm Architecture

    Understanding the Elm Architecture

    Elm is a functional programming language that is well-known for its robust architecture and its ability to build highly…

  • Binding Propagation in Prolog

    Binding Propagation in Prolog

    Prolog is a programming language that is well known for its ability to perform logical reasoning and search through…

  • Effect Rotation in ZIO

    Effect Rotation in ZIO

    ZIO is a popular functional programming library in Scala that has gained a lot of traction in recent years. One of the…

  • What Scala Can't Do

    What Scala Can't Do

    Today, Scala is a programming language that can use any Javascript library, any Java library, and any C/C++ library…

  • An Introduction to Shardcake

    An Introduction to Shardcake

    I recently found myself developing backend microservices with Scala and ZIO. I'm using ZIO-gRPC to implement the…

  • Transparency in Distributed Systems

    Transparency in Distributed Systems

    One of the most important theoretical goals of distributed systems is hiding the fact that resources and processes are…

社区洞察

其他会员也浏览了