3

I have a bunch of IOs, all of which may have either succeeded or failed:

val result: Seq[IO[MyFailure, MySuccess]] = ...

I need to summarize the results such that I can examine all the failures and all the successes together:

case class MySummary(failures: Seq[MyFailure], successes: Seq[MySuccess])
def makeSummary(results: Seq[IO[MyFailure, MySuccess]]): UIO[MySummary] = ???

At first this seemed quite similar to foreach, but upon closer inspection I see that that doesn't help here. For Future I'd normally use sequence, and for Seq[Either[A,B]] I could use partitionMap. What is the equivalent method in ZIO named?

Cory Klein
  • 51,188
  • 43
  • 183
  • 243

2 Answers2

3

I'm not familiar with ZIO, there might be a built-in way to achieve the same.

I would go with something like the following:

def makeSummary(results: Seq[IO[MyFailure, MySuccess]]): UIO[MySummary] = {
  results.foldLeft(ZIO.succeed(MySummary(Seq(), Seq()))) { (acc, io) =>
    acc.flatMap { summary => 
      io.fold(
        err => summary.copy(failures = summary.failures :+ err),
        suc => summary.copy(successes = summary.succcesses :+ suc)
      )
    }
  }
}
Gaël J
  • 11,274
  • 4
  • 17
  • 32
3

You can't examine all failures with Future.sequence, you will only get back the first failure if there is any.

The ZIO equivalent for Future.sequence would be ZIO.collectAll which is pretty much equivalent to ZIO.foreach(result)(identity).

If you really need to accumulate all errors then you should probably use ZIO.validate and for partitioning there's also ZIO.partition.

Some code for comparison:

  val result: Seq[IO[MyFailure, MySuccess]] = ???

  val withCollectAll: IO[MyFailure, Seq[MySuccess]] = ZIO.collectAll(result)
  val withForeach: IO[MyFailure, Seq[MySuccess]] = ZIO.foreach(result)(identity)
  val withValidate: IO[::[MyFailure], Seq[MySuccess]] = ZIO.validate(result)(identity) //`::` just means that the list cannot be empty
  val withPartition: UIO[(Iterable[MyFailure], Iterable[MySuccess])] = ZIO.partition(result)(identity)

Code for makeSummary with ZIO.partition:

  def makeSummary(results: Seq[IO[MyFailure, MySuccess]]): UIO[MySummary] =
    ZIO.partition(results)(identity).map { case (f, s) => MySummary(f.toSeq, s.toSeq) }
yangzai
  • 962
  • 5
  • 11
  • Ah you are indeed correct that `Future.sequence` is not precisely analagous to what I'm asking for with `makeSummary`, but `validate` is _exactly_ what I was looking for! Thank you very much. – Cory Klein Dec 28 '21 at 21:02
  • 2
    Ah, notable item of documentation on `validate` reads "_This combinator is lossy meaning that if there are errors all successes will be lost. To retain all information please use partition._". So in fact the method I need is probably actually `partition`. – Cory Klein Dec 28 '21 at 21:07
  • @CoryKlein yeah I just saw that what you actually wanted was to partition. Updated. – yangzai Dec 28 '21 at 21:12