Either and EitherT

Don’t choose, handle both cases

In one of the previous articles :https://scalatorblog.wordpress.com/2019/10/07/resist-the-side-effect-effect-types-1/ we talked about about handling errors by using the Either type effect,
as a data structure Either let us store the data we need but most importantly give us multiple ways to work with the data and chain on it, let’s see few examples:

import scala.util.Try
// import scala.util.Try

import cats.implicits._
// import cats.implicits._

def parseDouble(s: String): Either[String, Double] =
  Try(s.toDouble).map(Right(_)).getOrElse(Left(s"$s is not a number"))
// parseDouble: (s: String)Either[String,Double]

def divide(a: Double, b: Double): Either[String, Double] =
  Either.cond(b != 0, a / b, "Cannot divide by zero")
// divide: (a: Double, b: Double)Either[String,Double]

Map and flatMap (useful for chaining):

Since scala 2.12 Either is right-biased https://github.com/scala/scala/pull/5135, applying a map or a flatMap will try to access the right (success) side and apply the function, in case the either is left it will return it LEft

divide(5,8) map(_ + 1)
//res0: scala.util.Either[String,Double] = Right(1.625)
divide(5,0) map(_ + 1)
//res1: scala.util.Either[String,Double] = Left(Cannot divide by zero)


divide(5,8)  flatMap(res => Either.cond(res > 2, res, "result smaller then 2"))
//res3: scala.util.Either[String,Double] = Left(result smaller then 2)

divide(20,8) flatMap(res => Either.cond(res > 2, res, "result smaller then 2"))
//res4: scala.util.Either[String,Double] = Right(2.5)

divide(5,0) flatMap(res => Either.cond(res > 2, res, "result smaller then 2"))
// scala.util.Either[String,Double] = Left(Cannot divide by zero)

Fold (returned value):

Fold allows you to access and transform/ act on both sides

divide(5,0) fold(
 l => "just transfored this msg and reused the previous: " + l ,
 r => Right(r)
)
//res5: java.io.Serializable = just transfored this msg and reused the previousCannot divide by zero

divide(5,8) fold(
  l => "just transfored this msg and reused the previous: " + l ,
  r => Right(r)
)
//res6: java.io.Serializable = Right(0.625)

EitherT

We talked above about chaining and maybe one of the most used operators when dealing with containers (IO, Future) is For – comprehension ,as shown here when there is no container involved the code is seamless :

def divisionProgram(inputA: String, inputB: String): Either[String, Double] =
  for {
    a <- parseDouble(inputA)
    b <- parseDouble(inputB)
    result <- divide(a, b)
  } yield result
// divisionProgram: (inputA: String, inputB: String)Either[String,Double]

divisionProgram("4", "2") // Right(2.0)
// res0: Either[String,Double] = Right(2.0)

divisionProgram("a", "b") // Left("a is not a number")
// res1: Either[String,Double] = Left(a is not a number)


chaining multiple futures might pose a challenge :

def parseDoubleAsync(s: String): Future[Either[String, Double]] =
  Future.successful(parseDouble(s))
def divideAsync(a: Double, b: Double): Future[Either[String, Double]] =
  Future.successful(divide(a, b))

def divisionProgramAsync(inputA: String, inputB: String): Future[Either[String, Double]] =
  parseDoubleAsync(inputA) flatMap { eitherA =>
    parseDoubleAsync(inputB) flatMap { eitherB =>
      (eitherA, eitherB) match {
        case (Right(a), Right(b)) => divideAsync(a, b)
        case (Left(err), _) => Future.successful(Left(err))
        case (_, Left(err)) => Future.successful(Left(err))
      }
    }
  }

The code is less readable, more boilerplate code is required, we are mixing error code handling and the actual code.
Same code with EitherT

import cats.data.EitherT
// import cats.data.EitherT

import cats.implicits._
// import cats.implicits._

def divisionProgramAsync(inputA: String, inputB: String): EitherT[Future, String, Double] =
  for {
    a <- EitherT(parseDoubleAsync(inputA))
    b <- EitherT(parseDoubleAsync(inputB))
    result <- EitherT(divideAsync(a, b))
  } yield result
// divisionProgramAsync: (inputA: String, inputB: String)cats.data.EitherT[scala.concurrent.Future,String,Double]

divisionProgramAsync("4", "2").value
// res2: scala.concurrent.Future[Either[String,Double]] = Future(<not completed>)

divisionProgramAsync("a", "b").value
// res3: scala.concurrent.Future[Either[String,Double]] = Future(<not completed>)

EitherT is part of the cats library https://typelevel.org/cats/datatypes/eithert.html, we won’t talk much about it for the time being, we just need to use the proper imports. EitherT allows us to convert a F[Either[A, B]] to EitherT[F[_], A, B] , this allows us to access directly the right side of the either without the need to write boiler print code .
To extract the value, surprise we use .value
One of the conditions to use it is that the left side needs to be subType of the previous flatMap, according to the signature of the

  def flatMap[AA >: A, D](f: B => EitherT[F, AA, D])(implicit F: Monad[F]): EitherT[F, AA, D] =
    EitherT(F.flatMap(value) {
      case l @ Left(_) => F.pure(l.rightCast)
      case Right(b)    => f(b).value
    })


Magical Right !!!,
Either offer us constructors to build EitherT form: Either[A], Option[A], F[A]
For more information visit https://typelevel.org/cats/datatypes/eithert.html

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s