Basic Combinator

Let’s start with a predefined Check trait that will perform a check on a certain type. The result of the check would be either an error or a valid value.

Signature of the check is something like this :

trait Check[E, A] {
  def apply(value: A): Either[E, A]
}

We want to define basic combinator, a way to compose checks. Let’s start with defining and combinator. I think there are more elaborate discussion on the original book, to come up with the proper signature for this combinator.

I am planning to summarise the point here, but for now, go figure and fiddle around :)

Fiddle Around!

import cats.Semigroup
import cats.syntax.semigroup._

trait Check[E, A] {

  def apply(value: A): Either[E, A]

  def and(that: Check[E, A])(implicit s: Semigroup[E]): Check[E, A] = ??? 
}

type Error = String
implicit val commaSeparated = new Semigroup[Error] {
  def combine(a : Error, b: Error) = a +", "+b
}

type Checker[A] = Check[Error, A]

val mustBeGood = new Checker[String] {
  def apply(value: String) = ???
}

val mustBeLong = new Checker[String] {
  def apply(value: String) = ???
}

// Long enough and good
val mustBeGoodAndLong = mustBeGood and mustBeLong

println(mustBeGoodAndLong("Good enough"))

// Long enough but not good
println(mustBeGoodAndLong("Boobs"))

// Neither long nor good
println(mustBeGoodAndLong("Poop"))

import cats.Semigroup
import cats.syntax.semigroup._

trait Check[E, A] {

  def apply(value: A): Either[E, A]

  def and(that: Check[E, A])(implicit s: Semigroup[E]): Check[E, A] = {
    (value: A) => {
      (this.apply(value), that.apply(value)) match {
        case (Right(a), Right(_)) => Right(a)
        case (Left(a), Right(_))  => Left(a)
        case (Right(_), Left(b))  => Left(b)
        case (Left(a), Left(b))   => Left(a |+| b)
      }
    }
  }
}

type Error = String
implicit val commaSeparated = new Semigroup[Error] {
  def combine(a : Error, b: Error) = a +", "+b
}

type Checker[A] = Check[Error, A]

val mustBeGood = new Checker[String] {
  def apply(value: String) = if(value.contains("Good")) {
    Right(value)
  } else {
    Left("String must contain Good")
  }
}

val mustBeLong = new Checker[String] {
  def apply(value: String) = if(value.length >= 5) {
    Right(value)
  } else {
    Left("String must be longer than 5")
  }
}

// Long enough and good
val mustBeGoodAndLong = mustBeGood and mustBeLong

println(mustBeGoodAndLong("Good enough"))

// Long enough but not good
println(mustBeGoodAndLong("Boobs"))

// Neither long nor good
println(mustBeGoodAndLong("Poop"))