Copyright | (c) Fabian Birkmann 2020 |
---|---|
License | GPL-3 |
Maintainer | 99fabianb@sis.gl |
Stability | experimental |
Portability | POSIX |
Safe Haskell | None |
Language | Haskell2010 |
Types and functions to check properties of your data. To make best use of these functions you should check out Data.Functor.Contravariant. For an introduction see the README.
Synopsis
- newtype Unvalidated (a :: Type) = MkUnvalidated {
- unsafeValidate :: a
- unvalidated :: a -> Unvalidated a
- data CheckResult (e :: Type)
- checkResult :: a -> (Seq e -> a) -> CheckResult e -> a
- failsWith :: e -> CheckResult e
- failsNoMsg :: CheckResult e
- passed :: CheckResult e -> Bool
- failed :: CheckResult e -> Bool
- checkResultToEither :: a -> CheckResult e -> Either (Seq e) a
- newtype Check (e :: Type) (m :: Type -> Type) (a :: Type) = Check {
- runCheck :: Unvalidated a -> m (CheckResult e)
- type Check' e = Check e Identity
- pass :: Applicative m => Check e m a
- passOnRight :: Applicative m => (a -> Either b ()) -> Check e m b -> Check e m a
- mapError :: Functor m => (e -> e') -> Check e m a -> Check e' m a
- generalizeCheck :: Applicative m => Check' e a -> Check e m a
- validateBy :: Functor m => Check e m a -> Unvalidated a -> m (Either (Seq e) a)
- validateBy' :: Check' e a -> Unvalidated a -> Either (Seq e) a
- checking :: (a -> m (CheckResult e)) -> Check e m a
- checking' :: (a -> CheckResult e) -> Check' e a
- test :: Functor m => (a -> m Bool) -> (a -> e) -> Check e m a
- (?~>) :: Functor m => (a -> m Bool) -> (a -> e) -> Check e m a
- test' :: Applicative m => (a -> Bool) -> (a -> e) -> Check e m a
- (?>) :: Applicative m => (a -> Bool) -> (a -> e) -> Check e m a
- test_ :: Monad m => (a -> m Bool) -> e -> Check e m a
- (?~>>) :: Monad m => (a -> m Bool) -> e -> Check e m a
- test'_ :: Applicative m => (a -> Bool) -> e -> Check e m a
- (?>>) :: Applicative m => (a -> Bool) -> e -> Check e m a
- foldWithCheck :: (Foldable f, Applicative m) => Check e m a -> Check e m (f a)
- traverseWithCheck :: (Traversable t, Applicative m) => Check e m a -> Check e m (t a)
- type MultiCheck e m a = NP (NP (Check e m)) (Code a)
- joinMultiCheck :: forall a m e. (Applicative m, Generic a) => MultiCheck e m a -> Check e m a
- mapErrorsWithInfo :: forall e e' a m. (Functor m, HasDatatypeInfo a) => Proxy a -> (DatatypeName -> ConstructorName -> FieldName -> e -> e') -> MultiCheck e m a -> MultiCheck e' m a
- constructorCheck :: forall a m e xs. (Applicative m, Generic a) => (NP (Check e m) xs -> NS (NP (Check e m)) (Code a)) -> NP (Check e m) xs -> Check e m a
- hoist :: forall m n (b :: k). (MFunctor t, Monad m) => (forall a. m a -> n a) -> t m b -> t n b
- contramap :: Contravariant f => (a -> b) -> f b -> f a
- data NP (a :: k -> Type) (b :: [k]) where
- type DatatypeName = String
- type ConstructorName = String
- type FieldName = String
Unvalidated values
A newtype around unvalidated values so one cannot use the value until it is validated.
You can create an Unvalidated
via unvalidated
WARNING The Unvalidated
data construcotr should NOT be used in real code and is exported solely to be used in -XDeriving
-clauses
, but it is often more convient to write an orphan instance:
If for example you have a JSON api and want to validate incoming data, you can
write (using -XStandaloneDeriving, -XDerivingStrategies, -XDerivingVia
):
import Data.Aeson(FromJSON) deriving via (a :: Type) instance (FromJSON a) => FromJSON (Unvalidated a)
newtype Unvalidated (a :: Type) Source #
MkUnvalidated | Warning: Use |
|
Instances
unvalidated :: a -> Unvalidated a Source #
Types for checks
Check results
The result of (possibly many) checks. It is either valid or a sequence of all the errors that occurred during the check. The semigroup operation is eager to collect all possible erros.
data CheckResult (e :: Type) Source #
Instances
checkResult :: a -> (Seq e -> a) -> CheckResult e -> a Source #
A fold for CheckResult
failsWith :: e -> CheckResult e Source #
failsNoMsg :: CheckResult e Source #
Throwing an error without a message.
passed :: CheckResult e -> Bool Source #
failed :: CheckResult e -> Bool Source #
:: a | default value |
-> CheckResult e | |
-> Either (Seq e) a |
The Check type
The type of a (lifted) check. A Check
takes an unvalidated data and produces
a CheckResult
. It may need an additional context m
. If the context is trivial
('m ≡ Identity') helper types/functions are postfixed by an apostrophe `'`.
A Check
is not a validation function, as it does not produce any values
(to validated data using a Check
use validateBy
). The reason for this is that
it gives Check
some useful instances, as it now is contravariant in a
and not invariant in a
like e.g. `a -> Either b a`
- Contravariant
newtype Even = Even { getEven :: Int } checkEven :: Check' Text Even checkEven = (== 0) . (`mod` 2) . getEven ?> mappend "Number is not even: " . show newtype Odd = Odd { getOdd :: Int } checkOdd :: Check' Text Odd checkOdd = Even . (+1) . getOdd >$< checkEven
- Semigroup/Monoid: Allows for easy composition of checks
newtype EvenAndOdd = EvenAndOdd { getEvenAndOdd :: Int } checkevenAndOdd :: Check' Text EvenAndOdd checkEvenAndOdd = contramap (Even . getEvenAndOdd) checkEven <> contramap (Odd . getEvenAndOdd) checkOdd
- MFunctor: Changing the effect
import Data.List(isPrefixOf) newtype Url = Url { getUrl :: String } check404 :: Check () IO Url -- checks if the url returns 404 checkHttps :: Check' () Identity Url checkHttps = ("https" `isPrefixOf`) ?>> () checkUrl :: Check () IO Url checkUrl = check404 <> hoist generalize checkHttps
For more information see the README.
newtype Check (e :: Type) (m :: Type -> Type) (a :: Type) Source #
Check | |
|
passOnRight :: Applicative m => (a -> Either b ()) -> Check e m b -> Check e m a Source #
'passOnRight ignoreWhen
check
lets the argument pass when
ignoreWhen
returns `Right ()` and otherwise checks
with check
. It is a special case of choose
from Decidable
.
It gives an example for how Check
s expand to other datatypes since they are
Divisible
and Decidable
, see generalizing a check to lists:
checkList :: Applicative m => Check e m a -> Check e m [a] checkList c = passOnRight (\case [] -> Right () x:xs -> Left (x, xs)) ( divide id c (checkList c))
mapError :: Functor m => (e -> e') -> Check e m a -> Check e' m a Source #
Mapping over the error type.
generalizeCheck :: Applicative m => Check' e a -> Check e m a Source #
validateBy :: Functor m => Check e m a -> Unvalidated a -> m (Either (Seq e) a) Source #
Validate Unvalidated
data using a check.
validateBy' :: Check' e a -> Unvalidated a -> Either (Seq e) a Source #
validateBy
for trivial context.
Constructing checks
The general way to construct a Check
: Take the data to be checked and return a CheckResult
.
Construction by predicates
checking :: (a -> m (CheckResult e)) -> Check e m a Source #
checking' :: (a -> CheckResult e) -> Check' e a Source #
Constructing a check from a predicate (if a prediceate returns True
, the check passes) and a function constructing the error from the input. Naming conventions:
- Functions that work on trivial contexts are postfixed by an apostrophe `'`.
- Check constructors that discard the argument on error end with `_`.
- All infix operators start with
?
and end with>
(So?>
is the "normal" version). - Additional >: discards its argument:
?>>
,?~>>
. - Tilde works with non-trivial contexts:
?~>
,?~>>
.
Lifting Checks
foldWithCheck :: (Foldable f, Applicative m) => Check e m a -> Check e m (f a) Source #
Lift a check to a foldable
traverseWithCheck :: (Traversable t, Applicative m) => Check e m a -> Check e m (t a) Source #
Lift a check to a traversable
For ADTs
A MultiCheck
is a list of a list of checks, one for each field of each constructor. Do not be
scared by the types but read the section in the README
| A Multi-Check
for an ADT, one 'Check e m' for each field of each constructor, organized in Lists (see examples for construction)
joinMultiCheck :: forall a m e. (Applicative m, Generic a) => MultiCheck e m a -> Check e m a Source #
Combine all Check
s from a MultiCheck
into a single Check
for the datatype a
(given it has a Generic
instance).
mapErrorsWithInfo :: forall e e' a m. (Functor m, HasDatatypeInfo a) => Proxy a -> (DatatypeName -> ConstructorName -> FieldName -> e -> e') -> MultiCheck e m a -> MultiCheck e' m a Source #
Examples
checkNotEmpty = not . null ?>> "No name given" checkAdult = (>= 18) ?> printf "%s is too young; must be at least 18 years old" . show checkHttps = ("https://" `isPrefixOf`) ?> printf "Website '%s' is not secure: Missing 'https'" checkPet :: Check' Err Pet checkPet = joinMultiCheck ( (checkNotEmpty :* mempty :* Nil) -- checks for the fields of the first constructor :* (checkNotEmpty :* Nil) -- checks for the fields of the second constructor :* Nil ) -- outer list is also terminated by `Nil` checkProfile :: Check' Err Profile checkProfile = joinMultiCheck ( checkNotEmpty :* checkAdult :* checkPet :* foldWithCheck checkHttps -- `foldWithCheck` lifts a `Check` to a `Foldable`, in this case a list :* Nil ) -- only one constructor, so the outer list is a singleton list :* Nil
Change the error of a MultiCheck
using the information about the datatype.
Reexports
General
hoist :: forall m n (b :: k). (MFunctor t, Monad m) => (forall a. m a -> n a) -> t m b -> t n b #
Lift a monad morphism from m
to n
into a monad morphism from
(t m)
to (t n)
The first argument to hoist
must be a monad morphism, even though the
type system does not enforce this
contramap :: Contravariant f => (a -> b) -> f b -> f a #
SOP
data NP (a :: k -> Type) (b :: [k]) where #
An n-ary product.
The product is parameterized by a type constructor f
and
indexed by a type-level list xs
. The length of the list
determines the number of elements in the product, and if the
i
-th element of the list is of type x
, then the i
-th
element of the product is of type f x
.
The constructor names are chosen to resemble the names of the list constructors.
Two common instantiations of f
are the identity functor I
and the constant functor K
. For I
, the product becomes a
heterogeneous list, where the type-level list describes the
types of its components. For
, the product becomes a
homogeneous list, where the contents of the type-level list are
ignored, but its length still specifies the number of elements.K
a
In the context of the SOP approach to generic programming, an n-ary product describes the structure of the arguments of a single data constructor.
Examples:
I 'x' :* I True :* Nil :: NP I '[ Char, Bool ] K 0 :* K 1 :* Nil :: NP (K Int) '[ Char, Bool ] Just 'x' :* Nothing :* Nil :: NP Maybe '[ Char, Bool ]
Nil :: forall k (a :: k -> Type). NP a ('[] :: [k]) | |
(:*) :: forall k (a :: k -> Type) (x :: k) (xs :: [k]). a x -> NP a xs -> NP a (x ': xs) infixr 5 |
Instances
HTrans (NP :: (k1 -> Type) -> [k1] -> Type) (NP :: (k2 -> Type) -> [k2] -> Type) | |
Defined in Data.SOP.NP htrans :: forall c (xs :: l1) (ys :: l2) proxy f g. AllZipN (Prod NP) c xs ys => proxy c -> (forall (x :: k10) (y :: k20). c x y => f x -> g y) -> NP f xs -> NP g ys # hcoerce :: forall (f :: k10 -> Type) (g :: k20 -> Type) (xs :: l1) (ys :: l2). AllZipN (Prod NP) (LiftedCoercible f g) xs ys => NP f xs -> NP g ys # | |
HSequence (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP hsequence' :: forall (xs :: l) f (g :: k0 -> Type). (SListIN NP xs, Applicative f) => NP (f :.: g) xs -> f (NP g xs) # hctraverse' :: forall c (xs :: l) g proxy f f'. (AllN NP c xs, Applicative g) => proxy c -> (forall (a :: k0). c a => f a -> g (f' a)) -> NP f xs -> g (NP f' xs) # htraverse' :: forall (xs :: l) g f f'. (SListIN NP xs, Applicative g) => (forall (a :: k0). f a -> g (f' a)) -> NP f xs -> g (NP f' xs) # | |
HTraverse_ (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP hctraverse_ :: forall c (xs :: l) g proxy f. (AllN NP c xs, Applicative g) => proxy c -> (forall (a :: k0). c a => f a -> g ()) -> NP f xs -> g () # htraverse_ :: forall (xs :: l) g f. (SListIN NP xs, Applicative g) => (forall (a :: k0). f a -> g ()) -> NP f xs -> g () # | |
HCollapse (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP | |
HAp (NP :: (k -> Type) -> [k] -> Type) | |
HPure (NP :: (k -> Type) -> [k] -> Type) | |
All (Compose Eq f) xs => Eq (NP f xs) | |
(All (Compose Eq f) xs, All (Compose Ord f) xs) => Ord (NP f xs) | |
All (Compose Show f) xs => Show (NP f xs) | |
All (Compose Semigroup f) xs => Semigroup (NP f xs) | Since: sop-core-0.4.0.0 |
(All (Compose Monoid f) xs, All (Compose Semigroup f) xs) => Monoid (NP f xs) | Since: sop-core-0.4.0.0 |
All (Compose NFData f) xs => NFData (NP f xs) | Since: sop-core-0.2.5.0 |
Defined in Data.SOP.NP | |
type AllZipN (NP :: (k -> Type) -> [k] -> Type) (c :: a -> b -> Constraint) | |
Defined in Data.SOP.NP | |
type Same (NP :: (k1 -> Type) -> [k1] -> Type) | |
type SListIN (NP :: (k -> Type) -> [k] -> Type) | |
Defined in Data.SOP.NP | |
type UnProd (NP :: (k -> Type) -> [k] -> Type) | |
type Prod (NP :: (k -> Type) -> [k] -> Type) | |
type AllN (NP :: (k -> Type) -> [k] -> Type) (c :: k -> Constraint) | |
Defined in Data.SOP.NP | |
type CollapseTo (NP :: (k -> Type) -> [k] -> Type) a | |
Defined in Data.SOP.NP |
type DatatypeName = String #
The name of a datatype.
type ConstructorName = String #
The name of a data constructor.