Arjun Karnwal
Arjun Karnwal

Reputation: 379

Link Multiple Either's

Consider the scenario

trait Checker {
  def check()(implicit ec: ExecutionContext): Future[Either[String, Unit]]
} 

This trait is implemented by various class's.

Lets say

    class CheckerImpl1 extends Checker {
          override def check()(implicit ec: ExecutionContext): Future[Either[String, Unit]] = ???
        }
    class CheckerImpl2 extends Checker {
          override def check()(implicit ec: ExecutionContext): Future[Either[String, Unit]] = ???
        } 

Now, I need to define a new function that will call check() function for each of these implementation classes in sequence(sequence does not matter) and return a new either i.e. Future[Either[String, Unit]] where String here is concatenated string of left for the check() implementation result.

So if CheckerImpl1.check() returns Left("error1") and CheckerImpl2.check() returns Left("error2") then result of the new function will return Left("error1&error2") (& just separate two strings).

OR

So if CheckerImpl1.check() returns Right(()) and CheckerImpl2.check() returns Left("error2") then result of the new function will return Left("error2").

OR

So if CheckerImpl1.check() returns Right(()) and CheckerImpl2.check() returns Right(()) then result of the new function will return Right(()).

What I have done right now is

(CheckerImpl1.check(), CheckerImpl2.check())
      .mapN {
        case (Right(_), Right(_))     => Right(())
        case (Left(err), Right(_))    => Left(err)
        case (Right(_), Left(err))    => Left(err)
        case (Left(err1), Left(err2)) => Left(err1 ++ "&" ++ err2)))
      }

But this is not a ideal solution, because if I add more implementation, then I would need to add more of these case statement.

Is there a better way of doing this ?

Upvotes: 2

Views: 377

Answers (2)

bottaio
bottaio

Reputation: 5093

If I understand correctly, you eventually want to get Future[Either[String, Unit]] type. Why not just .sequence futures and .fold the results?

val checkers: List[Checker] = ???

Future.sequence(checkers.map(_.check()))
  .map { results => results.foldLeft(Right(()): Either[String, Unit]) {
    case (Left(acc), Left(err)) => Left(s"$acc&$err")
    case (Right(_), Left(err)) => Left(err)
    case (acc, Right(_)) => acc
  }}

The only code change you need now is to augment checkers list.


Somewhat more elegant using cats (if you are not familiar with kind projector plugin - that's where the * comes from).

import cats.implicilts._

checkers.map(_.check()).sequence
  .map { results =>
    results.map(_.toValidatedNec)
      .sequence[ValidatedNec[String, *], Unit]
      .leftMap(_.toList.mkString("&"))
      .map(_ => ())
      .toEither
  }

Upvotes: 2

jwvh
jwvh

Reputation: 51271

So you have a List of Future Eithers.

val lfe :List[Future[Either[String,Unit]]] = ???

To get all the Left strings together in one Future[String] you could do this...

val res :Future[String] =
  Future.sequence(lfe)
        .map(_.flatMap(_.fold(Some(_),_ => None)).mkString(" & "))

Upvotes: 3

Related Questions