Sunday, 10 June 2012

Validation: Non-breaking Error Handling

Non-breaking error handling was covered in few nice articles and presentations. Was reading one from Tony Morris. Solution for validation is looking little bit overcomplicated for me. 
Validation is a good example where non-breaking behaviour can be applied. Thinking on validation framework under application flow with immutable object states - may be this is a great idea for new Validation framework.Lets start from Validation, still duel nature, but Success doesn't has any state and Failure contains the list of errors. Failure is Monadic Zero but it has state and can change the state during "add" operation with help of Semigroup and Context Bound.
trait Semigroup[A] {
def append(a1: A, a2: A): A
}
sealed abstract class Validation[F: Semigroup] {
final def and(other: Validation[F]) = (this, other) match {
case (Failure(e1), Failure(e2)) => Failure(implicitly[Semigroup[F]].append(e1, e2))
case (Failure(e1), Success()) => Failure(e1)
case (Success(), Failure(e2)) => Failure(e2)
case (Success(), Success()) => Success
}
}
final case class Failure[F: Semigroup](e: F) extends Validation[F]
final case class Success[F: Semigroup]() extends Validation[F]
view raw Semigroup.scala hosted with ❤ by GitHub


We will present errors via List[String]:
implicit val listSemigroup = new Semigroup[List[String]] {
def append(a1: List[String], a2: List[String]): List[String] = {
a1 ::: a2
}
}
view raw implicit.scala hosted with ❤ by GitHub
Quickly introducing immutable structure with lenses that comes from Scala: Lenses:
case class Weapon(name: String, ammo: Long)
case class Soldier(rank: Int, weapon: Weapon)
case class Lens[R, F](get: R => F, set: (R, F) => R) {
def compose[Q](g: Lens[Q, R]): Lens[Q, F] =
Lens(get = get compose g.get,
set = (q, f) => g set(q, set(g get q, f)))
}
val weaponL = Lens[Soldier, Weapon](
get = _.weapon,
set = (s, w) => s.copy(weapon = w)
)
val ammoL = Lens[Weapon, Long](
get = _.ammo,
set = (w, a) => w.copy(ammo = a)
)
val nameL = Lens[Weapon, String](
get = _.name,
set = (w, n) => w.copy(name = n)
)
val soldierAmmoL = ammoL compose weaponL
val soldierWeaponName = nameL compose weaponL
view raw weapon.scala hosted with ❤ by GitHub
Validation functions:
def validateAmmo(ammo: => Long): Validation[List[String]] = {
if (ammo > 0) Success[List[String]]()
else Failure(List("Negative ammo"))
}
def validateName(name: => String): Validation[List[String]] = {
if (!name.isEmpty) Success[List[String]]()
else Failure(List("Empty Name"))
}
And finally the way to use it:
val blaster = new Weapon("", -99)
val soldier = new Soldier(4, blaster)
val validationR = validateAmmo(soldierAmmoL.get(soldier)) and validateName(soldierWeaponName.get(soldier))
What isn't done - the way how to link (DSL?) validations to lenses, without reflection or attributes. Are there any ideas?

No comments:

Post a Comment