Applicative Programming, Disjoint Unions, Semigroups and Non-breaking Errors
applicative-errors-scala.googlecode.com
applicative-errors-scala.googlecode.com
The punchline:
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
}
I'm puzzled by the author's choice of argument order, which means the call to the Person constructor at the end has to be reversed (arguments in reverse order with `f` at the end). In Haskell, using the built-in Applicative typeclass and flipping his definitions of <<star>> and vmap: instance Functor (Validation e) where
fmap = flip vmap
instance Semigroup e => Applicative (Validation e) where
pure = Success
(<*>) = flip (<<*>>)
you can write the constructor call in the natural order: Person <$> age <*> name <*> postcode
which is obviously analogous to what the call would look like without error handling: Person age name postcode
IMHO this shows the power of the technique described more clearly: that with a bit of plumbing up front, you can add arbitrarily sophisticated error handling behaviour without much disruption to existing code.One of my Haskell 'aha' moments was that you could do this with Parsec (a parsing library) to build data structures in a way that mirrors the grammar being parsed:
data Section = HostSection {hostNames :: [String], hostOptions :: [HostOption]}
hostSectionP :: CharParser st Section -- hostSectionP parses Chars and produces a Section
hostSectionP = HostSection <$> hostHeaderP <*> many1 hostOptionP
-- many1 is like '+' in regex