3

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].

pme
  • 14,156
  • 3
  • 52
  • 95

3 Answers3

2

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.
pme
  • 14,156
  • 3
  • 52
  • 95
Adam Fraser
  • 809
  • 5
  • 4
1

One option is to takeWhile and then fold:

import zio._
import zio.stream._

object Foo extends zio.App {
  override def run(args: List[String]): ZIO[zio.ZEnv, Nothing, Int] =
    Stream
      .fromIterable(List(2, 4, 6, 3, 5, 6))
      .takeWhile(_ % 2 == 0)
      .fold(0L)(_ + _)
      // Just to print the output before terminating
      .flatMap(res => zio.console.putStrLn(res.toString) *> ZIO.succeed(0))
}

I don't see a reason for this to return an Option[Long].

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • > I don't see a reason for this to return an Option[Long]. / I think that is the exception handling. so if not all numbers are even return with None > Exception. – pme Jan 14 '20 at 15:55
  • @pme Why would uneven numbers trigger an exception? – Yuval Itzchakov Jan 14 '20 at 15:59
  • As always maybe hard to find a good example;). See the original question: https://stackoverflow.com/questions/12892701/abort-early-in-a-fold – pme Jan 14 '20 at 16:27
1

With the idea of @Yuval Itzchakov and my remark that Option is to express an Exception (None).

I came up with this solution:

  def sumEvenNumbers(nums: Iterable[Int]): UIO[Option[Int]] = {
    Stream.fromIterable(nums)
      .run(Sink.foldLeftM(0) { (acc: Int, b: Int) =>
        if (b % 2 == 0)
          ZIO.succeed(acc + b)
        else
          ZIO.fail(acc)
      }).fold(
      _ => None, 
      v => Some(v)
    )
  }
  • Use foldLeftM - as soon as a number is not even - the fold fails.
  • Fold the result to Option
pme
  • 14,156
  • 3
  • 52
  • 95