Reputation: 379
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
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
Reputation: 51271
So you have a List
of Future
Either
s.
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