A. Validation.scala

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
*/