trait Semigroup[A] { // associative def append(a1: A, a2: A): A } object Semigroup { implicit def ListSemigroup[A]: Semigroup[List[A]] = new Semigroup[List[A]] { def append(a1: List[A], a2: List[A]) = a1 ::: a2 } } sealed trait Validation[E, X] { def map[Y](f: X => Y): Validation[E, Y] = this match { case Failure(e) => Failure(e) case Success(x) => Success(f(x)) } // applicative functor def <<*>>[Y](f: Validation[E, X => Y]) (implicit s: Semigroup[E]): Validation[E, Y] = (this, f) match { case (Failure(e1), Failure(e2)) => Failure(s append (e1, e2)) case (Failure(e1), Success(_)) => Failure(e1) case (Success(_), Failure(e2)) => Failure(e2) case (Success(x), Success(k)) => Success(k(x)) } } final case class Failure[E, X](e: E) extends Validation[E, X] final case class Success[E, X](x: X) extends Validation[E, X] //// // Age must be between 0 and 130 // Name must start with upper-case character // Postcode must be 4 digits case class Person(age: Int, name: String, postcode: String) object Person { def validAge(s: String): Validation[List[String], Int] = try { val a = s.toInt if(a < 0) Failure(List("Age must be greater than 0")) else if(a > 130) Failure(List("Age must be less than 130")) else Success(a) } catch { case e => Failure(List(e.toString)) } def validName(s: String): Validation[List[String], String] = if(s.headOption exists (_.isUpper)) Success(s) else Failure(List("Name must begin with a capital letter")) def validPostcode(s: String): Validation[List[String], String] = if(s.length == 4 && s.forall(_.isDigit)) Success(s) else Failure(List("Postcode must be 4 digits")) def main(args: Array[String]) { if(args.length < 3) println("Need at least three arguments") else { val f = (Person(_, _, _)).curried val age = validAge(args(0)) val name = validName(args(1)) val postcode = validPostcode(args(2)) postcode <<*>> (name <<*>> (age map f)) match { case Success(p) => println("We have a person: " + p) case Failure(e) => e foreach println } } } } /* $ scala Person 30 Fred 4000 We have a person: Person(30,Fred,4000) $ scala Person "-7" red 490000 Postcode must be 4 digits Name must begin with a capital letter Age must be greater than 0 $ scala Person boo Fred 490000 Postcode must be 4 digits java.lang.NumberFormatException: For input string: "boo" $ scala Person 30 "" 411x Postcode must be 4 digits Name must begin with a capital letter */