Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Left flatMap on EitherT

Let's say I have functions which return Future[Either[_, _] and I want to apply some of these functions in case of failures, that means apply them only to left side. The simplified example is:

def operation1: Future[Either[String, Int]] = Future.successful(Right(5))
def operation2: Future[Either[String, Int]] = Future.successful(Left("error"))
def operation2FallBackWork = Future.successful{
  println("Doing some revert stuff")
  Left("Error happened, but reverting was successful")
}

val res = for {
  res1 <- EitherT.fromEither(operation1)
  res2 <- EitherT.fromEither(operation2)//.leftFlatMap(operation2FallBackWork) -????
} yield res1 + res2

Await.result(res.toEither, 5 seconds)

How to achieve that?

like image 523
Nikita Avatar asked Oct 25 '25 03:10

Nikita


1 Answers

The closest thing to a leftFlatMap is MonadError's handleError, which has exactly the signature you'd expect from something called leftFlatMap (although note that you'll need to change the fallback operation to an EitherT and provide a constant function instead of passing it as-is). You can use the EitherT instance directly like this:

import scala.concurrent.{ Await, Future }
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scalaz._, Scalaz._

def operation1: Future[Either[String, Int]] = Future.successful(Right(5))
def operation2: Future[Either[String, Int]] = Future.successful(Left("error"))

def operation2FallBack: EitherT[Future, String, Int] = EitherT(
  Future.successful {
    println("Doing some revert stuff")
    "Error happened, but reverting was successful".left
  }
)

val E: MonadError[({ type L[x] = EitherT[Future, String, x] })#L, String] =
  implicitly

val res = for {
  a <- EitherT.fromEither(operation1)
  b <- E.handleError(EitherT.fromEither(operation2))(_ => operation2FallBack)
} yield a + b

Await.result(res.toEither, 5.seconds)

You can also use the syntax provided by MonadError to make it look like EitherT has a handleError method, although it takes a bit more ceremony to get the Scala compiler to recognize that your operations have the right shape:

import scala.concurrent.{ Await, Future }
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scalaz._, Scalaz._

type FE[x] = EitherT[Future, String, x]

def operation1: FE[Int] = EitherT(Future.successful(5.right))
def operation2: FE[Int] = EitherT(Future.successful("error".left))

def operation2FallBack: FE[Int] = EitherT(
  Future.successful {
    println("Doing some revert stuff")
    "Error happened, but reverting was successful".left
  }
)

val res = for {
  a <- operation1
  b <- operation2.handleError(_ => operation2FallBack)
} yield a + b

Await.result(res.toEither, 5.seconds)

I'd prefer this second version, but it's a matter of style and taste.

like image 76
Travis Brown Avatar answered Oct 26 '25 23:10

Travis Brown