Copyright |
(c) 2014 Chris Allen Edward Kmett
(c) 2018-2022 Kowainik |
---|---|
License | MPL-2.0 |
Maintainer | Kowainik <xrom.xkov@gmail.com> |
Stability | Stable |
Portability | Portable |
Safe Haskell | None |
Language | Haskell2010 |
Lightweight pure data validation based on
Applicative
and
Selective
functors.
Validation
allows to accumulate all errors instead of
short-circuting on the first error so you can display all possible
errors at once.
Common use-cases include:
- Validating each input of a form with multiple inputs.
- Performing multiple validations of a single value.
Validation
provides
modular
and
composable
interface which
means that you can implement validations for different pieces of your
data independently, and then combine smaller parts into the validation
of a bigger type. The below table illustrates main ways to combine two
Validation
s:
Typeclass | Operation ○ |
Failure
e ○
Failure
d
|
Success
a ○
Success
b
|
Failure
e ○
Success
a
|
Success
a ○
Failure
e
|
---|---|---|---|---|---|
Semigroup
|
<>
|
Failure
(e
<>
d)
|
Success
(a
<>
b)
|
Failure
e
|
Failure
e
|
Applicative
|
<*>
|
Failure
(e
<>
d)
|
Success
(a b)
|
Failure
e
|
Failure
e
|
Alternative
|
<|>
|
Failure
(e
<>
d)
|
Success
a
|
Success
a
|
Success
a
|
Selective
|
<*?
|
Failure
e
|
Selective
choice
|
Failure
e
|
Selective
choice
|
In other words, instances of different standard typeclasses provide various semantics which can be useful in different use-cases:
-
Semigroup
: accumulate bothFailure
andSuccess
with<>
. -
Monoid
:Success
that storesmempty
. -
Functor
: change the type insideSuccess
. -
Bifunctor
: change bothFailure
andSuccess
. -
Applicative
: apply function to values insideSuccess
and accumulate errors insideFailure
. -
Alternative
: return the firstSuccess
or accumulate all errors insideFailure
. -
Selective
: choose which validations to apply based on the value inside.
Synopsis
- data Validation e a
- isFailure :: Validation e a -> Bool
- isSuccess :: Validation e a -> Bool
- validation :: (e -> x) -> (a -> x) -> Validation e a -> x
- failures :: [ Validation e a] -> [e]
- successes :: [ Validation e a] -> [a]
- partitionValidations :: [ Validation e a] -> ([e], [a])
- fromFailure :: e -> Validation e a -> e
- fromSuccess :: a -> Validation e a -> a
- failure :: e -> Validation ( NonEmpty e) a
- failureIf :: Bool -> e -> Validation ( NonEmpty e) ()
- failureUnless :: Bool -> e -> Validation ( NonEmpty e) ()
- validationToEither :: Validation e a -> Either e a
- eitherToValidation :: Either e a -> Validation e a
- validateAll :: forall e b a f. ( Foldable f, Semigroup e) => f (a -> Validation e b) -> a -> Validation e a
- whenSuccess :: Applicative f => x -> Validation e a -> (a -> f x) -> f x
- whenFailure :: Applicative f => x -> Validation e a -> (e -> f x) -> f x
- whenSuccess_ :: Applicative f => Validation e a -> (a -> f ()) -> f ()
- whenFailure_ :: Applicative f => Validation e a -> (e -> f ()) -> f ()
- whenSuccessM :: Monad m => x -> m ( Validation e a) -> (a -> m x) -> m x
- whenFailureM :: Monad m => x -> m ( Validation e a) -> (e -> m x) -> m x
- whenSuccessM_ :: Monad m => m ( Validation e a) -> (a -> m ()) -> m ()
- whenFailureM_ :: Monad m => m ( Validation e a) -> (e -> m ()) -> m ()
- failureToMaybe :: Validation e a -> Maybe e
- successToMaybe :: Validation e a -> Maybe a
- maybeToFailure :: a -> Maybe e -> Validation e a
- maybeToSuccess :: e -> Maybe a -> Validation e a
Type
data Validation e a Source #
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
are used as follows:
Validation
e a
-
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
] User-
Either list of
String
error messages or a validated value of a customUser
type.
-
Either list of
-
Validation
(NonEmpty
UserValidationError) User- Similar 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.
Failure e |
Validation failure. The
|
Success a |
Successful validation result of type
|
Instances
Bitraversable Validation Source # |
Similar to
Examples
|
Defined in Validation bitraverse :: Applicative f => (a -> f c) -> (b -> f d) -> Validation a b -> f ( Validation c d) Source # |
|
Bifoldable Validation Source # |
Similar to
Examples
|
Defined in Validation bifold :: Monoid m => Validation m m -> m Source # bifoldMap :: Monoid m => (a -> m) -> (b -> m) -> Validation a b -> m Source # bifoldr :: (a -> c -> c) -> (b -> c -> c) -> c -> Validation a b -> c Source # bifoldl :: (c -> a -> c) -> (c -> b -> c) -> c -> Validation a b -> c Source # |
|
Bifunctor Validation Source # |
Similar to
Examples
|
Defined in Validation bimap :: (a -> b) -> (c -> d) -> Validation a c -> Validation b d Source # first :: (a -> b) -> Validation a c -> Validation b c Source # second :: (b -> c) -> Validation a b -> Validation a c Source # |
|
NFData2 Validation Source # | |
Defined in Validation liftRnf2 :: (a -> ()) -> (b -> ()) -> Validation a b -> () Source # |
|
(NoValidationMonadError, Semigroup e) => Monad ( Validation e) Source # |
⚠️ CAUTION ⚠️ This instance is for custom error display only.
It's not possible to implement lawful
In case it is used by mistake, the user will see the following:
|
Defined in Validation (>>=) :: Validation e a -> (a -> Validation e b) -> Validation e b Source # (>>) :: Validation e a -> Validation e b -> Validation e b Source # return :: a -> Validation e a Source # |
|
Functor ( Validation e) Source # |
Allows changing the value inside
Examples
|
Defined in Validation fmap :: (a -> b) -> Validation e a -> Validation e b Source # (<$) :: a -> Validation e b -> Validation e a Source # |
|
Semigroup e => Applicative ( Validation e) Source # |
This instance is the most important instance for the
Examples
Implementations of all functions are lazy and they correctly work if some arguments are not fully evaluated.
|
Defined in Validation pure :: a -> Validation e a Source # (<*>) :: Validation e (a -> b) -> Validation e a -> Validation e b Source # liftA2 :: (a -> b -> c) -> Validation e a -> Validation e b -> Validation e c Source # (*>) :: Validation e a -> Validation e b -> Validation e b Source # (<*) :: Validation e a -> Validation e b -> Validation e a Source # |
|
Foldable ( Validation e) Source # |
Examples
|
Defined in Validation fold :: Monoid m => Validation e m -> m Source # foldMap :: Monoid m => (a -> m) -> Validation e a -> m Source # foldMap' :: Monoid m => (a -> m) -> Validation e a -> m Source # foldr :: (a -> b -> b) -> b -> Validation e a -> b Source # foldr' :: (a -> b -> b) -> b -> Validation e a -> b Source # foldl :: (b -> a -> b) -> b -> Validation e a -> b Source # foldl' :: (b -> a -> b) -> b -> Validation e a -> b Source # foldr1 :: (a -> a -> a) -> Validation e a -> a Source # foldl1 :: (a -> a -> a) -> Validation e a -> a Source # toList :: Validation e a -> [a] Source # null :: Validation e a -> Bool Source # length :: Validation e a -> Int Source # elem :: Eq a => a -> Validation e a -> Bool Source # maximum :: Ord a => Validation e a -> a Source # minimum :: Ord a => Validation e a -> a Source # sum :: Num a => Validation e a -> a Source # product :: Num a => Validation e a -> a Source # |
|
Traversable ( Validation e) Source # |
Traverse values inside
Examples
|
Defined in Validation traverse :: Applicative f => (a -> f b) -> Validation e a -> f ( Validation e b) Source # sequenceA :: Applicative f => Validation e (f a) -> f ( Validation e a) Source # mapM :: Monad m => (a -> m b) -> Validation e a -> m ( Validation e b) Source # sequence :: Monad m => Validation e (m a) -> m ( Validation e a) Source # |
|
( Semigroup e, Monoid e) => Alternative ( Validation e) Source # |
This instance implements the behaviour when the first
Examples
|
Defined in Validation empty :: Validation e a Source # (<|>) :: Validation e a -> Validation e a -> Validation e a Source # some :: Validation e a -> Validation e [a] Source # many :: Validation e a -> Validation e [a] Source # |
|
NFData e => NFData1 ( Validation e) Source # | |
Defined in Validation liftRnf :: (a -> ()) -> Validation e a -> () Source # |
|
Semigroup e => Selective ( Validation e) Source # |
Examples
To understand better, how
When user enters a password in some form, we want to check the following conditions:
As in the previous usage example with form validation, let's introduce a custom data type to represent all possible errors.
And, again, we can implement independent functions to validate all these cases:
And we can easily compose all these checks into single validation for
However, if we try using this function, we can notice a problem immediately:
Due to the nature of the
You may say that check for empty password is redundant because empty
password is a special case of a short password. However, when using
This behaviour could be achieved easily if
First, we need to write a function that checks whether the password is empty:
Now we can use the
With this implementation we achieved our desired behavior:
|
Defined in Validation select :: Validation e ( Either a b) -> Validation e (a -> b) -> Validation e b Source # |
|
Generic1 ( Validation e :: Type -> Type ) Source # | |
Defined in Validation type Rep1 ( Validation e) :: k -> Type Source # from1 :: forall (a :: k). Validation e a -> Rep1 ( Validation e) a Source # to1 :: forall (a :: k). Rep1 ( Validation e) a -> Validation e a Source # |
|
( Eq e, Eq a) => Eq ( Validation e a) Source # | |
Defined in Validation (==) :: Validation e a -> Validation e a -> Bool Source # (/=) :: Validation e a -> Validation e a -> Bool Source # |
|
( Data e, Data a) => Data ( Validation e a) Source # | |
Defined in Validation gfoldl :: ( forall d b. Data d => c (d -> b) -> d -> c b) -> ( forall g. g -> c g) -> Validation e a -> c ( Validation e a) Source # gunfold :: ( forall b r. Data b => c (b -> r) -> c r) -> ( forall r. r -> c r) -> Constr -> c ( Validation e a) Source # toConstr :: Validation e a -> Constr Source # dataTypeOf :: Validation e a -> DataType Source # dataCast1 :: Typeable t => ( forall d. Data d => c (t d)) -> Maybe (c ( Validation e a)) Source # dataCast2 :: Typeable t => ( forall d e0. ( Data d, Data e0) => c (t d e0)) -> Maybe (c ( Validation e a)) Source # gmapT :: ( forall b. Data b => b -> b) -> Validation e a -> Validation e a Source # gmapQl :: (r -> r' -> r) -> r -> ( forall d. Data d => d -> r') -> Validation e a -> r Source # gmapQr :: forall r r'. (r' -> r -> r) -> r -> ( forall d. Data d => d -> r') -> Validation e a -> r Source # gmapQ :: ( forall d. Data d => d -> u) -> Validation e a -> [u] Source # gmapQi :: Int -> ( forall d. Data d => d -> u) -> Validation e a -> u Source # gmapM :: Monad m => ( forall d. Data d => d -> m d) -> Validation e a -> m ( Validation e a) Source # gmapMp :: MonadPlus m => ( forall d. Data d => d -> m d) -> Validation e a -> m ( Validation e a) Source # gmapMo :: MonadPlus m => ( forall d. Data d => d -> m d) -> Validation e a -> m ( Validation e a) Source # |
|
( Ord e, Ord a) => Ord ( Validation e a) Source # | |
Defined in Validation compare :: Validation e a -> Validation e a -> Ordering Source # (<) :: Validation e a -> Validation e a -> Bool Source # (<=) :: Validation e a -> Validation e a -> Bool Source # (>) :: Validation e a -> Validation e a -> Bool Source # (>=) :: Validation e a -> Validation e a -> Bool Source # max :: Validation e a -> Validation e a -> Validation e a Source # min :: Validation e a -> Validation e a -> Validation e a Source # |
|
( Show e, Show a) => Show ( Validation e a) Source # | |
Defined in Validation |
|
Generic ( Validation e a) Source # | |
Defined in Validation from :: Validation e a -> Rep ( Validation e a) x Source # to :: Rep ( Validation e a) x -> Validation e a Source # |
|
( Semigroup e, Semigroup a) => Semigroup ( Validation e a) Source # |
Examples
|
Defined in Validation (<>) :: Validation e a -> Validation e a -> Validation e a Source # sconcat :: NonEmpty ( Validation e a) -> Validation e a Source # stimes :: Integral b => b -> Validation e a -> Validation e a Source # |
|
( Semigroup e, Semigroup a, Monoid a) => Monoid ( Validation e a) Source # |
Examples
|
Defined in Validation mempty :: Validation e a Source # mappend :: Validation e a -> Validation e a -> Validation e a Source # mconcat :: [ Validation e a] -> Validation e a Source # |
|
( NFData e, NFData a) => NFData ( Validation e a) Source # | |
Defined in Validation rnf :: Validation e a -> () Source # |
|
type Rep1 ( Validation e :: Type -> Type ) Source # | |
Defined in Validation
type
Rep1
(
Validation
e ::
Type
->
Type
) =
D1
('
MetaData
"Validation" "Validation" "validation-selective-0.1.0.2-Jm2KMRrxGIAHArdVHY9pvJ" '
False
) (
C1
('
MetaCons
"Failure" '
PrefixI
'
False
) (
S1
('
MetaSel
('
Nothing
::
Maybe
Symbol
) '
NoSourceUnpackedness
'
NoSourceStrictness
'
DecidedLazy
) (
Rec0
e))
:+:
C1
('
MetaCons
"Success" '
PrefixI
'
False
) (
S1
('
MetaSel
('
Nothing
::
Maybe
Symbol
) '
NoSourceUnpackedness
'
NoSourceStrictness
'
DecidedLazy
)
Par1
))
|
|
type Rep ( Validation e a) Source # | |
Defined in Validation
type
Rep
(
Validation
e a) =
D1
('
MetaData
"Validation" "Validation" "validation-selective-0.1.0.2-Jm2KMRrxGIAHArdVHY9pvJ" '
False
) (
C1
('
MetaCons
"Failure" '
PrefixI
'
False
) (
S1
('
MetaSel
('
Nothing
::
Maybe
Symbol
) '
NoSourceUnpackedness
'
NoSourceStrictness
'
DecidedLazy
) (
Rec0
e))
:+:
C1
('
MetaCons
"Success" '
PrefixI
'
False
) (
S1
('
MetaSel
('
Nothing
::
Maybe
Symbol
) '
NoSourceUnpackedness
'
NoSourceStrictness
'
DecidedLazy
) (
Rec0
a)))
|
How to use
This section contains the typical
Validation
usage example. Let's say we
have a form with fields where you can input your login information.
>>>
:{
data Form = Form { formUserName :: !String , formPassword :: !String } :}
This
Form
data type can represent values of some text fields on the
web page or inside the GUI application. Our goal is to create a value of
the custom
User
data type from the
Form
fields.
First, let's define our
User
type and additional
newtype
s for more
type safety.
>>>
:{
newtype UserName = UserName { unUserName :: String } deriving newtype (Show) :}
>>>
:{
newtype Password = Password { unPassword :: String } deriving newtype (Show) :}
>>>
:{
data User = User { userName :: !UserName , userPassword :: !Password } deriving stock (Show) :}
We can easily create a
User
from the
Form
in the
unsafe
way by wrapping
each form field into the corresponding
newtype
:
>>>
:{
unsafeUserFromForm :: Form -> User unsafeUserFromForm Form{..} = User { userName = UserName formUserName , userPassword = Password formPassword } :}
However, this conversion is unsafe (as name suggests) since
Form
can
contain
invalid
data. So, before creating a
User
we want to check
whether all
Form
fields satisfy our preconditions. Specifically:
- User name must not be empty.
- Password should be at least 8 characters long.
- Password should contain at least 1 digit.
Validation
offers
modular
and
composable
way of defining and
outputting all validation failures which means:
- Modular : define validation checks for different fields independently.
- Composable : combine smaller validations easily into a validation of a bigger type.
Before implementing
Form
validation, we need to introduce a type for
representing our validation errors. It is a good practice to define
all possible errors as a single sum type, so let's go ahead:
>>>
:{
data FormValidationError = EmptyName | ShortPassword | NoDigitPassword deriving stock (Show) :}
With
Validation
we can define checks for individual fields
independently and compose them later. First, let's start with defining
validation for the name:
>>>
:{
validateName :: String -> Validation (NonEmpty FormValidationError) UserName validateName name = UserName name <$ failureIf (null name) EmptyName :}
You can notice a few things about this function:
-
All errors are collected in
NonEmpty
, since we want to have guarantees that in case of errors we have at least one failure. -
It wraps the result into
UserName
to tell that validation is passed.
Let's see how this function works:
>>>
validateName "John"
Success "John">>>
validateName ""
Failure (EmptyName :| [])
Since
Validation
provides
modular
interface for defining checks,
we now can define all validation functions for the password
separately:
>>>
:{
validateShortPassword :: String -> Validation (NonEmpty FormValidationError) Password validateShortPassword password = Password password <$ failureIf (length password < 8) ShortPassword :}
>>>
:{
validatePasswordDigit :: String -> Validation (NonEmpty FormValidationError) Password validatePasswordDigit password = Password password <$ failureUnless (any isDigit password) NoDigitPassword :}
After we've implemented validations for different
Form
fields, it's
time to combine them together!
Validation
offers several ways to
compose different validations. These ways are provided via different
instances of common Haskell typeclasses, specifically:
Semigroup
allows combining values inside both
Failure
and
Success
but this requires both values to implement the
Semigroup
instance. This doesn't fit our goal, since
Password
can't have a
reasonble
Semigroup
instance.
Alternative
returns first
Success
or combines all
Failure
s. We
can notice that
Alternative
also doesn't work for us here.
In our case we are interested in collecting all possible errors and
returning
Success
only when all checks are passed. Fortunately,
Applicative
is exactly what we need here. So we can use the
*>
operator to compose all checks for password:
>>>
:{
validatePassword :: String -> Validation (NonEmpty FormValidationError) Password validatePassword password = validateShortPassword password *> validatePasswordDigit password :}
Let's see how it works:
>>>
validatePassword "abcd"
Failure (ShortPassword :| [NoDigitPassword])>>>
validatePassword "abcd1"
Failure (ShortPassword :| [])>>>
validatePassword "abcd12345"
Success "abcd12345"
The
validation
library provides several convenient combinators, so
you can write the password check in a shorter way:
validatePassword ::String
->Validation
(NonEmpty
FormValidationError) Password validatePassword =fmap
Password .validateAll
[ (`failureIf
` ShortPassword) . (< 8) .length
, (`failureUnless
` NoDigitPassword) .any
isDigit ]
After we've implemented validations for all fields, we can compose
them together to produce validation for the whole
User
. As before,
we are going to use the
Applicative
instance:
>>>
:{
validateForm :: Form -> Validation (NonEmpty FormValidationError) User validateForm Form{..} = User <$> validateName formUserName <*> validatePassword formPassword :}
And it works like a charm:
>>>
validateForm (Form "" "")
Failure (EmptyName :| [ShortPassword,NoDigitPassword])>>>
validateForm (Form "John" "abc")
Failure (ShortPassword :| [NoDigitPassword])>>>
validateForm (Form "Jonh" "qwertypassword")
Failure (NoDigitPassword :| [])>>>
validateForm (Form "Jonh" "qwertypassword123")
Success (User {userName = "Jonh", userPassword = "qwertypassword123"})
Interface functions
isFailure :: Validation e a -> Bool Source #
Predicate on if the given
Validation
is
Failure
.
>>>
isFailure (Failure 'e')
True>>>
isFailure (Success 'a')
False
isSuccess :: Validation e a -> Bool Source #
Predicate on if the given
Validation
is
Success
.
>>>
isSuccess (Success 'a')
True>>>
isSuccess (Failure 'e')
False
validation :: (e -> x) -> (a -> x) -> Validation e a -> x Source #
Transforms the value of the given
Validation
into
x
using provided
functions that can transform
Failure
and
Success
value into the resulting
type respectively.
>>>
let myValidation = validation (<> " world!") (show . (* 10))
>>>
myValidation (Success 100)
"1000">>>
myValidation (Failure "Hello")
"Hello world!"
failures :: [ Validation e a] -> [e] Source #
Filters out all
Failure
values into the new list of
e
s from the given
list of
Validation
s.
Note that the order is preserved.
>>>
failures [Failure "Hello", Success 1, Failure "world", Success 2, Failure "!" ]
["Hello","world","!"]
successes :: [ Validation e a] -> [a] Source #
Filters out all
Success
values into the new list of
a
s from the given
list of
Validation
s.
Note that the order is preserved.
>>>
successes [Failure "Hello", Success 1, Failure "world", Success 2, Failure "!" ]
[1,2]
partitionValidations :: [ Validation e a] -> ([e], [a]) Source #
Redistributes the given list of
Validation
s into two lists of
e
s and
e
s, where the first list contains all values of
Failure
s and the second
one —
Success
es correspondingly.
Note that the order is preserved.
>>>
partitionValidations [Failure "Hello", Success 1, Failure "world", Success 2, Failure "!" ]
(["Hello","world","!"],[1,2])
fromFailure :: e -> Validation e a -> e Source #
Returns the contents of a
Failure
-value or a default value otherwise.
>>>
fromFailure "default" (Failure "failure")
"failure">>>
fromFailure "default" (Success 1)
"default"
fromSuccess :: a -> Validation e a -> a Source #
Returns the contents of a
Success
-value or a default value otherwise.
>>>
fromSuccess 42 (Success 1)
1>>>
fromSuccess 42 (Failure "failure")
42
NonEmpty
combinators
When using
Validation
, we often work with the
NonEmpty
list of errors, and
those lists will be concatenated later.
The following functions aim to help with writing more concise code.
For example, instead of (perfectly fine) code like:
>>>
:{
validateNameVerbose :: String -> Validation (NonEmpty String) String validateNameVerbose name | null name = Failure ("Empty Name" :| []) | otherwise = Success name :}
one can write simply:
>>>
:{
validateNameSimple :: String -> Validation (NonEmpty String) String validateNameSimple name = name <$ failureIf (null name) "Empty Name" :}
failure :: e -> Validation ( NonEmpty e) a Source #
failureUnless :: Bool -> e -> Validation ( NonEmpty e) () Source #
Returns a
Failure
unless the given predicate is
True
.
Returns
in case of the predicate is satisfied.
Success
()
Similar to
failureIf
with the reversed predicate.
failureUnless
p ≡failureIf
(not p)
>>>
let shouldFail = (==) "I am a failure"
>>>
failureUnless (shouldFail "I am a failure") "doesn't matter"
Success ()>>>
failureUnless (shouldFail "I am NOT a failure") "I told you so"
Failure ("I told you so" :| [])
Either
conversion
Validation
is usually compared to the
Either
data type due to the similarity
in structure, nature and use case. Here is a quick table you can relate to, in
order to see the main properties and differences between these two data types:
Either
|
Validation
|
|
---|---|---|
Error result |
Left
|
Failure
|
Successful result |
Right
|
Success
|
Applicative
instance
|
Stops on the first
Left
|
Aggregates all
Failure
s
|
Monad
instance
|
Lawful instance | Cannot exist |
Comparison in example
For the sake of better illustration of the difference between
Either
and
Validation
, let's go through the example of how parsing is done with the usage of
these types.
Our goal is to parse two given
String
s and return their sum in case if both of
them are valid
Int
s. If any of the inputs is failing to be parsed we should
return the
ParseError
which we are introducing right now:
>>>
:{
newtype ParseError = ParseError { nonParsedString :: String } deriving stock (Show) :}
Let's first implement the parsing of single input in the
Either
context:
>>>
:{
parseEither :: String -> Either ParseError Int parseEither input = case readMaybe @Int input of Just x -> Right x Nothing -> Left $ ParseError input :}
And the final function for
Either
looks like this:
>>>
:{
parseSumEither :: String -> String -> Either ParseError Int parseSumEither str1 str2 = do let x = parseEither str1 let y = parseEither str2 liftA2 (+) x y :}
Let's now test it in action.
>>>
parseSumEither "1" "2"
Right 3>>>
parseSumEither "NaN" "42"
Left (ParseError {nonParsedString = "NaN"})>>>
parseSumEither "15" "Infinity"
Left (ParseError {nonParsedString = "Infinity"})>>>
parseSumEither "NaN" "infinity"
Left (ParseError {nonParsedString = "NaN"})
Note
how in the case of both failed parsing we got only the first
NaN
.
To finish our comparison, let's implement the same functionality using
Validation
properties.
>>>
:{
parseValidation :: String -> Validation (NonEmpty ParseError) Int parseValidation input = case readMaybe @Int input of Just x -> Success x Nothing -> failure $ ParseError input :}
>>>
:{
parseSumValidation :: String -> String -> Validation (NonEmpty ParseError) Int parseSumValidation str1 str2 = do let x = parseValidation str1 let y = parseValidation str2 liftA2 (+) x y :}
It looks almost completely identical except for the resulting type —
. But let's see if they behave the
same way:
Validation
(
NonEmpty
ParseError)
Int
>>>
parseSumValidation "1" "2"
Success 3>>>
parseSumValidation "NaN" "42"
Failure (ParseError {nonParsedString = "NaN"} :| [])>>>
parseSumValidation "15" "infinity"
Failure (ParseError {nonParsedString = "infinity"} :| [])>>>
parseSumValidation "NaN" "infinity"
Failure (ParseError {nonParsedString = "NaN"} :| [ParseError {nonParsedString = "infinity"}])
As expected, with
Validation
we got
all
parse
Failure
s we received on
the way.
Combinators
We are providing several functions for better integration with the
Either
related code in this section.
validationToEither :: Validation e a -> Either e a Source #
Transform a
Validation
into an
Either
.
>>>
validationToEither (Success "whoop")
Right "whoop"
>>>
validationToEither (Failure "nahh")
Left "nahh"
eitherToValidation :: Either e a -> Validation e a Source #
Transform an
Either
into a
Validation
.
>>>
eitherToValidation (Right "whoop")
Success "whoop"
>>>
eitherToValidation (Left "nahh")
Failure "nahh"
Combinators
validateAll :: forall e b a f. ( Foldable f, Semigroup e) => f (a -> Validation e b) -> a -> Validation e a Source #
Validate all given checks in a
Foldable
. Returns the
Success
of the
start element when all checks are successful.
A basic example of usage could look like this:
> let validatePassword =validateAll
[ validateEmptyPassword , validateShortPassword ] >validateAll
"VeryStrongPassword"Success
"VeryStrongPassword" >validateAll
""Failure
(EmptyPassword :| [ShortPassword])
When* functions
whenSuccess :: Applicative f => x -> Validation e a -> (a -> f x) -> f x Source #
Applies the given action to
Validation
if it is
Success
and returns the
result. In case of
Failure
the default value is returned.
>>>
whenSuccess "bar" (Failure "foo") (\a -> "success!" <$ print a)
"bar"
>>>
whenSuccess "bar" (Success 42) (\a -> "success!" <$ print a)
42 "success!"
whenFailure :: Applicative f => x -> Validation e a -> (e -> f x) -> f x Source #
Applies the given action to
Validation
if it is
Failure
and returns the
result. In case of
Success
the default value is returned.
>>>
whenFailure "bar" (Failure 42) (\a -> "foo" <$ print a)
42 "foo"
>>>
whenFailure "bar" (Success 42) (\a -> "foo" <$ print a)
"bar"
whenSuccess_ :: Applicative f => Validation e a -> (a -> f ()) -> f () Source #
Applies given action to the
Validation
content if it is
Success
.
Similar to
whenSuccess
but the default value is
()
.
>>>
whenSuccess_ (Failure "foo") print
>>>
whenSuccess_ (Success 42) print
42
whenFailure_ :: Applicative f => Validation e a -> (e -> f ()) -> f () Source #
Applies given action to the
Validation
content if it is
Failure
.
Similar to
whenFailure
but the default value is
()
.
>>>
whenFailure_ (Success 42) putStrLn
>>>
whenFailure_ (Failure "foo") putStrLn
foo
whenSuccessM :: Monad m => x -> m ( Validation e a) -> (a -> m x) -> m x Source #
Monadic version of
whenSuccess
.
Applies monadic action to the given
Validation
in case of
Success
.
Returns the resulting value, or provided default.
>>>
whenSuccessM "bar" (pure $ Failure "foo") (\a -> "success!" <$ print a)
"bar"
>>>
whenSuccessM "bar" (pure $ Success 42) (\a -> "success!" <$ print a)
42 "success!"
whenFailureM :: Monad m => x -> m ( Validation e a) -> (e -> m x) -> m x Source #
Monadic version of
whenFailure
.
Applies monadic action to the given
Validation
in case of
Failure
.
Returns the resulting value, or provided default.
>>>
whenFailureM "bar" (pure $ Failure 42) (\a -> "foo" <$ print a)
42 "foo"
>>>
whenFailureM "bar" (pure $ Success 42) (\a -> "foo" <$ print a)
"bar"
whenSuccessM_ :: Monad m => m ( Validation e a) -> (a -> m ()) -> m () Source #
Monadic version of
whenSuccess_
.
Applies monadic action to the given
Validation
in case of
Success
.
Similar to
whenSuccessM
but the default is
()
.
>>>
whenSuccessM_ (pure $ Failure "foo") print
>>>
whenSuccessM_ (pure $ Success 42) print
42
whenFailureM_ :: Monad m => m ( Validation e a) -> (e -> m ()) -> m () Source #
Monadic version of
whenFailure_
.
Applies monadic action to the given
Validation
in case of
Failure
.
Similar to
whenFailureM
but the default is
()
.
>>>
whenFailureM_ (pure $ Success 42) putStrLn
>>>
whenFailureM_ (pure $ Failure "foo") putStrLn
foo
Maybe
conversion
failureToMaybe :: Validation e a -> Maybe e Source #
Maps
Failure
of
Validation
to
Just
.
>>>
failureToMaybe (Failure True)
Just True>>>
failureToMaybe (Success "aba")
Nothing
successToMaybe :: Validation e a -> Maybe a Source #
Maps
Success
of
Validation
to
Just
.
>>>
successToMaybe (Failure True)
Nothing>>>
successToMaybe (Success "aba")
Just "aba"
maybeToFailure :: a -> Maybe e -> Validation e a Source #
maybeToSuccess :: e -> Maybe a -> Validation e a Source #