Scala Cats: EitherT

Reading Time: 2 minutes

In this blog, I will discuss about EitherT which is another concept of Scala Cats library.

Before discussing about EitherT, lets discuss Either first then EitherT which helps us in resolving the issue or boilerplate we have due to Either.

Either

Either can be used for error handling in most situations. However, when Either is placed into effectful types such as Option or Future, a large amount of boilerplate is required to handle errors

import scala.util.Try
import cats.implicits._

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

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

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

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

If both the methods are written in asynchronous way which may return Future[Either[String, Double]] then for-comprehension can not be used. So let’s see what else we can change in above code to make it appropriate according to the requirement.

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

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))
      }
    }
  }

EitherT

EitherT[F[_], A, B] is a lightweight wrapper for F[Either[A, B]] that makes it easy to compose Either‘s and F‘s together. To use EitherT, values of EitherFA, and B are first converted into EitherT, and the resulting EitherT values are then composed using combinators

import cats.data.EitherT
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("4", "2").value
// res2: Future[Either[String, Double]] = Future(Success(Right(2.0)))
divisionProgramAsync("a", "b").value
// res3: Future[Either[String, Double]] = Future(Success(Left(a is not a number)))

There are different ways we can modify our methods so that it might return the value in different way. So lets just explore few such ways.

From A or B to EitherT[F, A, B]

val number: EitherT[Option, String, Int] = EitherT.rightT(5) 
val error: EitherT[Option, String, Int] = EitherT.leftT("Not a number")

From F[A] or F[B] to EitherT[F, A, B]

val numberO: Option[Int] = Some(5)
val errorO: Option[String] = Some("Not a number")

val number: EitherT[Option, String, Int] = EitherT.right(numberO)
val error: EitherT[Option, String, Int] = EitherT.left(errorO)

Extracting an F[Either[A, B]] from an EitherT[F, A, B]

val errorT: EitherT[Future, String, Int] = EitherT.leftT("foo")
// errorT: EitherT[Future, String, Int] = EitherT(Future(Success(Left(foo))))

val error: Future[Either[String, Int]] = errorT.value
// error: Future[Either[String, Int]] = Future(Success(Left(foo)))

Hope this blog will help you. I am also new to this concept so have taken a reference from documentation to get a better understanding about this concept.

Happy Blogging!

References:-


Knoldus-blog-footer-image

Written by 

Sanjana Aggarwal is a Software Consultant at Knoldus Software LLP, having experience of more than 1.5 years. She has done MCA from Bharati Vidyapeeth Institute of Computer Application and Management, Paschim Vihar. She has a decent knowledge of Java, Scala, Lagom, Kafka, Cassandra, Cocuhbase, MongoDB, Akka and Akka HTTP. She loves reading books and cooking and traveling.