Validation is a polymorphic sum type for storing either all validation failures or validation success. Unlike Either, which returns only the first error, Validation accumulates all errors using the Semigroup typeclass.
Usually type variables in Validation e a are used as follows:
e: is a list or set of failure messages or values of some error data type.a: is some domain type denoting successful validation result.
Some typical use-cases:
Validation [String] UserEither list of String error messages or a validated value of a custom
Usertype.
Validation (NonEmpty UserValidationError) UserSimilar to previous example, but list of failures guaranteed to be non-empty in case of validation failure, and it stores values of some custom error type.
Instances23Bifoldable, Bifunctor, Bitraversable, NFData2, Generic1, Monad, …
Bifoldable ValidationDefined in validation-selective-0.2.0.0 · ValidationBifunctor ValidationDefined in validation-selective-0.2.0.0 · ValidationBitraversable ValidationDefined in validation-selective-0.2.0.0 · ValidationSimilar to Traversable but traverses both Failure and Success with given effectful computations.
Examples
Example5 expressions parseInt = readMaybe :: String -> Maybe Intbitraverse listToMaybe parseInt (Success "42")Just (Success 42)bitraverse listToMaybe parseInt (Success "int")Nothingbitraverse listToMaybe parseInt (Failure [15])Just (Failure 15)bitraverse listToMaybe parseInt (Failure [])Nothing
NFData2 ValidationDefined in validation-selective-0.2.0.0 · ValidationGeneric1 (Validation e)Defined in validation-selective-0.2.0.0 · Validation(NoValidationMonadError, Semigroup e) => Monad (Validation e)Defined in validation-selective-0.2.0.0 · Validation⚠️CAUTION⚠️ This instance is for custom error display only.
It's not possible to implement lawful Monad instance for Validation.
In case it is used by mistake, the user will see the following:
Example1 expression Success 42 >>= \n -> if even n then Success n else Failure ["Not even"]...... Type 'Validation' doesn't have lawful 'Monad' instance which means that you can't use 'Monad' methods with 'Validation'....
Functor (Validation e)Defined in validation-selective-0.2.0.0 · ValidationAllows changing the value inside Success with a given function.
Examples
Example2 expressions fmap (+1) (Success 9)Success 10fmap (+1) (Failure ["wrong"])Failure ["wrong"]
Semigroup e => Applicative (Validation e)Defined in validation-selective-0.2.0.0 · ValidationThis instance is the most important instance for the Validation data type. It's responsible for the many implementations. And it allows to accumulate errors while performing validation or combining the results in the applicative style.
Examples
Example5 expressions success1 = Success 9 :: Validation [String] Intsuccess2 = Success 15 :: Validation [String] IntsuccessF = Success (* 2) :: Validation [String] (Int -> Int)failure1 = Failure ["WRONG"] :: Validation [String] Intfailure2 = Failure ["FAIL"] :: Validation [String] Int
Example6 expressions successF <*> success1Success 18successF <*> failure1Failure ["WRONG"](+) <$> success1 <*> success2Success 24(+) <$> failure1 <*> failure2Failure ["WRONG","FAIL"]liftA2 (+) success1 failure1Failure ["WRONG"]liftA3 (,,) failure1 success1 failure2Failure ["WRONG","FAIL"]
Implementations of all functions are lazy and they correctly work if some arguments are not fully evaluated.
Example4 expressions failure1 *> failure2Failure ["WRONG","FAIL"]isFailure $ failure1 *> failure2TrueepicFail = error "Impossible validation" :: Validation [String] IntisFailure $ failure1 *> epicFailTrue
Foldable (Validation e)Defined in validation-selective-0.2.0.0 · ValidationFoldable for Validation allows folding values inside Success.
Examples
Example2 expressions fold (Success [16])[16]fold (Failure "WRONG!" :: Validation String [Int])[]
Traversable (Validation e)Defined in validation-selective-0.2.0.0 · ValidationTraverse values inside Success with some effectful computation.
Examples
Example4 expressions parseInt = readMaybe :: String -> Maybe Inttraverse parseInt (Success "42")Just (Success 42)traverse parseInt (Success "int")Nothingtraverse parseInt (Failure ["42"])Just (Failure ["42"])
Monoid e => Alternative (Validation e)Defined in validation-selective-0.2.0.0 · ValidationThis instance implements the behaviour when the first Success is returned. Otherwise all Failures are combined.
Examples
Example4 expressions success1 = Success [9] :: Validation [String] [Int]success2 = Success [15] :: Validation [String] [Int]failure1 = Failure ["WRONG"] :: Validation [String] [Int]failure2 = Failure ["FAIL"] :: Validation [String] [Int]
Example3 expressions success1 <|> success2Success [9]failure1 <|> failure2Failure ["WRONG","FAIL"]failure2 <|> success2Success [15]
NFData e => NFData1 (Validation e)Defined in validation-selective-0.2.0.0 · ValidationSemigroup e => Selective (Validation e)Defined in validation-selective-0.2.0.0 · ValidationSelective functors from the selective package. This instance allows choosing which validations to apply based on value inside. Validation can't have a lawful Monad instance but it's highly desirable to have the monadic behavior in cases when you want future checks depend on previous values. Selective allows to circumvent this limitation by providing the desired behavior.
Examples
To understand better, how Selective can be helpful, let's consider a typical usage example with validating passwords.
Example1 expression :{newtype Password = Password { unPassword :: String } deriving stock (Show):}
When user enters a password in some form, we want to check the following conditions:
Password must not be empty.
Password must contain at least 8 characters.
Password must contain at least 1 digit.
As in the previous usage example with form validation, let's introduce a custom data type to represent all possible errors.
Example1 expression :{data PasswordValidationError = EmptyPassword | ShortPassword | NoDigitPassword deriving stock (Show):}
And, again, we can implement independent functions to validate all these cases:
Example1 expression type PasswordValidation = Validation (NonEmpty PasswordValidationError) PasswordExample1 expression :{validateEmptyPassword :: String -> PasswordValidationvalidateEmptyPassword password = Password password <$ failureIf (null password) EmptyPassword:}
Example1 expression :{validateShortPassword :: String -> PasswordValidationvalidateShortPassword password = Password password <$ failureIf (length password < 8) ShortPassword:}
Example1 expression :{validatePasswordDigit :: String -> PasswordValidationvalidatePasswordDigit password = Password password <$ failureUnless (any isDigit password) NoDigitPassword:}
And we can easily compose all these checks into single validation for
Passwordusing Applicative instance:Example1 expression :{validatePassword :: String -> PasswordValidationvalidatePassword password = validateEmptyPassword password *> validateShortPassword password *> validatePasswordDigit password:}
However, if we try using this function, we can notice a problem immediately:
Example1 expression validatePassword ""Failure (EmptyPassword :| [ShortPassword,NoDigitPassword])
Due to the nature of the Applicative instance for Validation, we run all checks and combine all possible errors. But you can notice that if password is empty, it doesn't make sense to run other validations. The fact that the password is empty implies that password is shorter than 8 characters.
You may say that check for empty password is redundant because empty password is a special case of a short password. However, when using Validation, we want to display readable and friendly errors to users, so they know how to fix errors and can act correspondingly.
This behaviour could be achieved easily if Validation had the Monad instance. But it can't have a lawful Monad instance. Fortunately, the Selective instance for Validation can help with our problem. But to solve it, we need to write our password validation in a slightly different way.
First, we need to write a function that checks whether the password is empty:
Example1 expression :{checkEmptyPassword :: String -> Validation e BoolcheckEmptyPassword = Success . null:}
Now we can use the
ifSfunction from theselectivepackage to branch on the result ofcheckEmptyPassword:Example1 expression :{validatePassword :: String -> PasswordValidationvalidatePassword password = ifS (checkEmptyPassword password) (failure EmptyPassword) (validateShortPassword password *> validatePasswordDigit password):}
With this implementation we achieved our desired behavior:
Example4 expressions validatePassword ""Failure (EmptyPassword :| [])validatePassword "abc"Failure (ShortPassword :| [NoDigitPassword])validatePassword "abc123"Failure (ShortPassword :| [])validatePassword "security567"Success (Password {unPassword = "security567"})
(Eq e, Eq a) => Eq (Validation e a)Defined in validation-selective-0.2.0.0 · Validation(Data e, Data a) => Data (Validation e a)Defined in validation-selective-0.2.0.0 · Validation(Ord e, Ord a) => Ord (Validation e a)Defined in validation-selective-0.2.0.0 · Validation(Show e, Show a) => Show (Validation e a)Defined in validation-selective-0.2.0.0 · ValidationGeneric (Validation e a)Defined in validation-selective-0.2.0.0 · Validation(Semigroup e, Semigroup a) => Semigroup (Validation e a)Defined in validation-selective-0.2.0.0 · ValidationSemigroup allows merging multiple Validations into single one by combining values inside both Failure and Success. The <> operator merges two Validations following the below rules:
If both values are Failures, returns a new Failure with accumulated errors.
If both values are Successful, returns a new Success with combined success using Semigroup for values inside Success.
If one value is Failure and another one is Success, then Failure is returned.
Examples
Example4 expressions success1 = Success [9] :: Validation [String] [Int]success2 = Success [15] :: Validation [String] [Int]failure1 = Failure ["WRONG"] :: Validation [String] [Int]failure2 = Failure ["FAIL"] :: Validation [String] [Int]
Example4 expressions success1 <> success2Success [9,15]failure1 <> failure2Failure ["WRONG","FAIL"]success1 <> failure1Failure ["WRONG"]failure2 <> success1 <> success2 <> failure1Failure ["FAIL","WRONG"]
(Semigroup e, Monoid a) => Monoid (Validation e a)Defined in validation-selective-0.2.0.0 · Validationmempty :: Validation e aisSuccesswhich storesmempty :: ato be consistent with the Semigroup instance.Examples
Example1 expression mempty :: Validation String [Bool]Success []
(NFData e, NFData a) => NFData (Validation e a)Defined in validation-selective-0.2.0.0 · Validationtype Rep (Validation e a) = D1 ('MetaDataDefined in validation-selective-0.2.0.0 · Validation"Validation"
"Validation"
"validation-selective-0.2.0.0-BLtLhIrZ39o5Jzj6FfbGYk"
'False) (C1 ('MetaCons"Failure"
'PrefixI 'False) (S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 e)) :+: C1 ('MetaCons"Success"
'PrefixI 'False) (S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 a)))type Rep1 (Validation e) = D1 ('MetaDataDefined in validation-selective-0.2.0.0 · Validation"Validation"
"Validation"
"validation-selective-0.2.0.0-BLtLhIrZ39o5Jzj6FfbGYk"
'False) (C1 ('MetaCons"Failure"
'PrefixI 'False) (S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 e)) :+: C1 ('MetaCons"Success"
'PrefixI 'False) (S1 ('MetaSel 'Nothing 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) Par1))