In the previous post I discussed how I transitioned from various ways of implementing validation. On the last one involving the combination of the
State types I said that while I like this one the best so far it still shows a bit more plumbing then I would like. In this post I will show the improved version of this I came to after.
My issue from the previous post was that I had to manage the plumbing explicitly when it came to combining my validations into the form validation. A way to resolve this issue is to make use of monad transformers, which give you tools to manage this plumbing.
import Control.Applicative import Control.Monad.Trans.Class import Control.Monad.Trans.State import Control.Monad.Trans.Maybe type Age = Int type Username = String data Form = Form Age Username deriving (Show) type FormState a = MaybeT (State [String]) a pushErr :: String -> FormState a pushErr s = lift (modify (s:)) >> empty validateAge :: Age -> FormState Age validateAge age | age > 0 = return age | otherwise = pushErr "Age needs to be positive" validateUsername :: Username -> FormState Username validateUsername username | username `elem` usernames = pushErr "Username is already taken" | otherwise = return username where usernames = ["foo", "bar", "baz"] validateForm age username = Form <$> validateAge age <*> validateUsername username
This code is similar to the previous version, but you’ll notive some slight differences. The main difference is that I used
MaybeT instead of
Maybe. With that monad transformer I’m able to make a monad with
Maybe like features while still using other monads. In this case, the main places this shows up is in the
pushErr function and the final form validation function. Now the
pushErr function explicitly lifts itself into the
State monad to handle errors, and the form validation values can be threaded like how it is done with the