On veut valider un utilisateur qui s'inscrit.
case class User(id: UUID,
                bio: String,
                birthday: String)
case class CheckedUser(id: UUID,
                       bio: String,
                       birthday: DateTime)
def validate(user: User): CheckedUser
case class User(id: UUID,
                bio: String,
                birthday: String)
3 échecs possibles :
def validate(user: User): CheckedUser
def validate(user: User): Option[CheckedUser] = {
  // Si tout est ok
  Some(checkedUser)
  // Sinon
  None
}
Suffisant si on n'a pas besoin d'information sur l'erreur
val checkedUser: Option[CheckedUser] = ???
// Pas bien (erreur de compilation)
checkedUser.id
// Pas bien (peut lancer une exception)
checkedUser.get.id
// Bien
checkedUser match {
  case Some(u) => u.id
  case None    => ???
  // Obligé de traiter le cas où ça échoue
}
// Validation Error sealed trait VE case class BioTooLong(length: Int) extends VE case object InvalidBirthdayFormat extends VE case object ImpossibleBirthday extends VE
sealed aide le compilateur à vérifier tous les cas possibles
def validate(user: User):
            Either[VE, CheckedUser] = {
  // Si tout est ok
  Right(checkedUser)
  // Sinon
  Left(error)
}
val checkedUser: Either[VE, CheckedUser] = ???
checkedUser match {
  case Right(u)                    => u.id
  case Left(BioTooLong(length))    => ???
  case Left(InvalidBirthdayFormat) => ???
  case Left(ImpossibleBirthday)    => ???
  // Warning du compilateur si on oublie un cas
}
def validate(user: User): Try[CheckedUser] = {
  // Si tout est ok
  Success(checkedUser)
  // Sinon
  Failure(error)
}
Similaire à Either :
Similaire à Try, mais asynchrone
val f: Future[T] = ??? val value: Option[Try[T]] = f.value
import scalaz.{\/, -\/, \/-}
def validate(user: User): VE \/ CheckedUser = {
  // Si tout est ok
  \/-(checkedUser)
  // Sinon
  -\/(error)
}
Similaire à Either, mais part du principe que la valeur intéressante est à droite (right-biased)
eitherVal.right.map(???) disjunctionVal.map(???)
import scalaz.{ValidationNel, Success, Failure}
import scalaz.syntax.validation._
import scalaz.syntax.applicative._
def checkBioLength(u: User):
                  ValidationNel[VE, User] = {
  val l = u.bio.length
  if (l < 5) u.success
  else BioTooLong(l).failureNel
}
def checkBirthdayFormat(u: User):
                ValidationNel[VE, User] = ???
def validate(user: User):
            ValidationNel[VE, CheckedUser] = {
  val cbl = checkBioLength(user)
  val cbf = checkBirthdayFormat(user)
  (cbl |@| cbf) { (u, _) =>
    CheckedUser(???)
  }
}
Permet d'accumuler les erreurs lorsqu'on fait des validations indépendantes
GitHub : propensive/rapture-core
Utiliser correctement ces types pour gérer les erreurs permet :
Twitter : @d_sferruzza
Slides sur GitHub :