# Lift your constructors for healthy data

December 16, 2015

When constructing a new data type, it makes sense that we’d want to have some validation to ensure that our inputs are correct. Say we have some

type Name = String
type Age = Integer
data Person = Person Name Age deriving Show

Clearly, we want to make sure that someone’s age isn’t less than 0 – and I’m pretty sure we also don’t want to have a Bobby Tables situation on our hands. It’s also a good idea not to leave the name empty. To keep things simple, we’ll stick to the latter and make sure that our humans are valid:

type ValidPerson a = Either [PersonInvalid] a

and let’s create our error type and validation checks:

data PersonInvalid = NameEmpty | AgeTooLow deriving (Eq, Show)

ageOkay :: Age -> Either [PersonInvalid] Age
ageOkay age = case age > 0 of
True -> Right age
False -> Left [AgeTooLow]

nameOkay :: Name -> Either [PersonInvalid] Name
nameOkay name = case name /= "" of
True -> Right name
False -> Left [NameEmpty]

This all seems sane, but now how are we going to make our people? Well, the first approach would be to create some function that checks all of our conditions:

mkPerson' :: ValidatePerson Name -> ValidatePerson Age -> ValidatePerson Person
mkPerson' (Right nameOk) (Right ageOk) = Right (Person nameOk ageOk)
mkPerson' _              (Left badAge) = Left badAge
…but seeing as Either is an applicative monad, we can use lift to clean this up a smidge:
mkPerson :: Name -> Age -> Validation [PersonInvalid] Person
mkPerson name age = liftA2 Person (nameOkay name) (ageOkay age)