3

I am facing an issue while I am trying to resolve the result of a method. More specifically I have:

def methodA(): Future[Either[Error, Seq[A]]]

and at some point, I want to call this method for each element of a list and merge the result. Something like this:

val tes: Seq[Future[Either[Error, Seq[A]]]] = relevantRounds.map(round =>
            methodA()
          )

Do you know how I can resolve the Seq[Future[Either[Error, Seq[A]]]]?

So what I finally want is Future[Either[Error, Seq[A]]] with a sequence which contains the result of the whole list.

pik4
  • 1,283
  • 3
  • 21
  • 56
  • 1
    Did you mean `Future[Seq[Either[Error, Seq[A]]]]` ( a simple `Future.traverse(relevantRounds)(methodA)` would give you that) or `Future[Either[Error, Seq[A]]]` (which flattens the individual sequences into one or errors out the whole thing if any of them failed)? And do you want to stop calling `methodA` when one of them failed (or call them all in parallel upfront)? – Thilo May 06 '19 at 07:53
  • 1
    What if some of `Either`s contains error? – talex May 06 '19 at 07:53
  • 2
    So you want 1 `Error` result, from any of the `methodA()` calls, to override (and discard) all the `A` results? – jwvh May 06 '19 at 07:58
  • @jwvh yes, so either one Error or list of A – pik4 May 06 '19 at 08:05
  • @Thilo Yes, I would like to stop calling methodA when one of them failed. – pik4 May 06 '19 at 08:09
  • 1
    The type signature of your `tes` value suggests that all Futures have already been launched. Futures can't be cancelled. Their results might be ignored/discarded but they will run their course. If you really want to stop calling `methodA()` after the first `Left` result or `Future` failure (not the same thing) then you'll need to go synchronous. – jwvh May 06 '19 at 08:56

2 Answers2

2

You probably looking for

  def combine[A](s: Seq[Future[Either[Error, Seq[A]]]]) = {
    Future.sequence(s)
      .map(x => {
        x.foldRight(Right(Seq()): Either[Error, Seq[A]]) {
          (e, acc) => for (xs <- acc.right; x <- e.right) yield x ++ xs
        }
      }
      )
  }

If you want functions to be executed only if previous is succeed then use this

  def combine[A](s: Seq[() => Future[Either[Error, Seq[A]]]]): Future[Either[Error, Seq[A]]] =
    combine(Seq(), s)

  def combine[A](acc: Seq[A], s: Seq[() => Future[Either[Error, Seq[A]]]]): Future[Either[Error, Seq[A]]] = s match {
    case x +: Nil =>
      val v = x.apply()
      v.andThen {
        case Success(Right(r)) => Success(Right(acc ++ r))
        case Success(Left(l)) => Success(Left(l))
        case Failure(f) => Failure(f)
      }
    case x +: xs =>
      val v = x.apply()
      v.andThen {
        case Success(Right(r)) => combine(acc ++ r, xs)
        case Success(Left(l)) => Success(Left(l))
        case Failure(f) => Failure(f)
      }
  }
talex
  • 17,973
  • 3
  • 29
  • 66
2

Try .flatTraverse (wrap Future[Either[Error, Vector[A]]] with Nested[Future, Either[Error, ?], Vector[A]]).

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cats.data.Nested
import cats.syntax.traverse._
import cats.syntax.functor._
import cats.instances.vector._
import cats.instances.future._
import cats.instances.either._

trait A
def methodA(): Future[Either[Error, Seq[A]]] = ???

trait Round
val relevantRounds: Seq[Round] = ???

val tes: Future[Either[Error, Seq[A]]] = 
  relevantRounds.toVector
    .flatTraverse(round => 
      Nested(methodA()).map(_.toVector)
    ).value

Vector is used instead of Seq because of the reason.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66