Search code examples
scalascala-cats

How to reduce Seq of functions that return Either with short circuit behavior?


Take the following example (adapted from the cats Either documentation).

Three validation functions are being applied in sequence each returning an Either. If the application of any of the functions fails a Left value is returned.

object Validators {
  def isBiggerThan5(i: Int): Either[Exception, Int] =
    if (i > 5) Either.right(i)
    else Either.left(new NumberFormatException(s"${i} is smaller than 5."))

  def isSmallerThan20(i: Int): Either[Exception, Int] =
    if (i < 20) Either.right(i)
    else Either.left(new NumberFormatException(s"${i} is bigger than 20."))

  def isEven(i: Int): Either[Exception, Int] =
    if (i % 2 == 0) Either.right(i)
    else Either.left(new NumberFormatException(s"${i} is not an even number."))
}
import Validators._

def magic(i: Int): Either[Exception, Int] =
  isBiggerThan5(i).flatMap(isSmallerThan20).flatMap(isEven)

Now imagine the three validation functions are passed as arguments:

type Ops = Int => Either[Exception, Int]

def magic(ops: Seq[Ops])(s: String): Either[Exception, String] =
  ??? // How to apply ops in sequence with short circuit behavior?

println(magic(Seq(isBiggerThan5, isSmallerThan20, isEven)))

How can we evaluate the Seq[Ops] one after another with short circuit behavior? I have played around with Seq.foldLeft or Seq.reduce but could not quite figure it out.

I am not interested in error accumulation. It should simply fail on the first validator that returns a Left.


Solution

  • foldLeftM should work.

    import cats.syntax.all._
    def verify(i: Int, ops: Vector[Ops]): Either[Exception, Int] =
      ops.foldLeftM(i)((i, o) => o(i))