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.
foldLeftM
should work.
import cats.syntax.all._
def verify(i: Int, ops: Vector[Ops]): Either[Exception, Int] =
ops.foldLeftM(i)((i, o) => o(i))