Search code examples
scalafunctional-programmingfoldzio

Abort early in a fold with ZIO


I wanted to add an answer to abort-early-in-a-fold for ZIO.

So I took the solution with cats: cats solution

def sumEvenNumbers(nums: Stream[Int]): Option[Long] = {
  import cats.implicits._
  nums.foldM(0L) {
    case (acc, c) if c % 2 == 0 => Some(acc + c)
    case _ => None
  }
}

How can this be achieved with ZIO?

The closest I got:

  new DefaultRuntime {}
    .unsafeRun(sumEvenNumbers(List(2,4,6,3,5,6)))

  def sumEvenNumbers(nums: Iterable[Int]): ZIO[Any, Nothing, Int] = {
    stream.Stream.fromIterable(nums)
      .run(Sink.fold(0)(s => s % 2 == 0) { (a: Int, b: Int) => (a + b, Chunk.empty)
      })
  }

But that gives me: 15 instead of 12. So it seems to short circuit but it takes a number too many. And it is an Int not Option[Int].


Solution

  • A solution without zio.stream.Stream:

      def sumEvenNumbers(as: Iterable[Int]): UIO[Option[Int]] =
        ZIO
          .foldLeft(as)(0)((s, a) => if (a % 2 == 0) ZIO.succeed(s + a) else ZIO.fail(s))
          .option
    
    • Use .foldLeft - as soon as a number is not even - the fold fails.
    • Use .option to merge the Error channel to the Success channel to an Option.