Previously, I concluded that using Either[String, Unit] is quite a nice way to compose predicates. Since then I have noticed some other patterns in my code that I would argue is an improvement.

We will still keep the “for-comprehension” style as I find that it makes the code more readable due to the shorter indentation compared to nested if else blocks.

The main difference is rather than defining functions that take the input and produce the monad, we use implicits to turn a boolean (or any other types as we shall see) into the monad we want for composition.

Instead of writing

// From previous code snippet
type Rejection = Either[String, Unit]
val isNotMagicNumber: Int => Rejection = x =>
if (x != 42) Right(()) else Left("Rejected because it is 42")

We shall define an implicit conversion from Boolean to Rejection then we can compose them as follows.

implicit class BooleanOps(b: Boolean) {
    def assertTrue[A](a: A): Either[A, Unit] = ???

val goodX = for {
    _ <- (x != 42).assertTrue("Rejected because it is 42")
    _ <- (x % 2 == 0).assertTrue("Rejected because it is odd")
} yield x

An added benefit of this is it compose well with non boolean checks too. For example if we want to get a value from a map which may not have the value, then we can compose it with

// val lookup: Map[Int, A] = ???
for {
    _ <- lookup.get(x).toRight(s"Missing key $x from lookup table")
} ...

Final remarks

While the idea of composing these predicates is quite similar to before. I find that using implicit classes makes it clearer what’s being tested.