Safe Haskell | None |
---|---|
Language | Haskell2010 |
Validity
is used to specify additional invariants upon values that are not
enforced by the type system.
Let's take an example.
Suppose we were to implement a type Prime
that represents prime integers.
If you were to completely enforce the invariant that the represented number is
a prime, then we could use Natural
and only store the index of the
given prime in the infinite sequence of prime numbers.
This is very safe but also very expensive if we ever want to use the number,
because we would have to calculcate all the prime numbers until that index.
Instead we choose to implement Prime
by a newtype Prime = Prime Int
.
Now we have to maintain the invariant that the Int
that we use to represent
the prime is in fact positive and a prime.
The Validity
typeclass allows us to specify this invariant (and enables
testing via the genvalidity
libraries:
https://hackage.haskell.org/package/genvalidity ):
instance Validity Prime where validate (Prime n) = isPrime n <?@> "The 'Int' is prime." isValid (Prime n) = isPrime n
If certain typeclass invariants exist, you can make these explicit in the
validity instance as well.
For example, 'Fixed a' is only valid if a
has an HasResolution
instance,
so the correct validity instance is HasResolution a => Validity (Fixed a)
.
- class Validity a where
- triviallyValid :: a -> Bool
- trivialValidation :: a -> Validation
- isValidByValidating :: Validity a => a -> Bool
- check :: Bool -> String -> Validation
- (<?!>) :: Validity a => a -> String -> Validation
- annotate :: Validity a => a -> String -> Validation
- (<?@>) :: Bool -> String -> Validation
- validateByChecking :: Validity a => String -> a -> Validation
- validateByCheckingName :: Validity a => String -> a -> Validation
- validateByCheckingDefault :: Validity a => a -> Validation
- isInvalid :: Validity a => a -> Bool
- constructValid :: Validity a => a -> Maybe a
- constructValidUnsafe :: (Show a, Validity a) => a -> a
- newtype Validation = Validation {}
- data ValidationChain
- checkValidity :: Validity a => a -> Either [ValidationChain] a
- prettyValidation :: Validity a => a -> Either String a
- class Monoid a where
Documentation
class Validity a where Source #
A class of types that have additional invariants defined upon them that aren't enforced by the type system
Purpose
validate
checks whether a given value is a valid value and reports all
reasons why the given value is not valid if that is the case.
isValid
only checks whether a given value is a valid value of its type.
Instantiating Validity
To instantiate Validity
, one has to implement both isValid
and
validate
.
Start by implementing validate
. Use the helper functions below to define
all the reasons why a given value would be a valid value of its type.
Then define `isValid = isValidbyValidating' for now.
Example:
newtype Even = Even Int instance Validity Even validate (Event i) even i <?@> "The contained 'Int' is even." isValid = isValidByValidating
If it turns out that, at this point, isValid
is too slow for your taste,
you can replace the implementation of isValid
by a custom implementation.
However, it is important that this isValid
implementation has exactly
the same semantics as isValidbyValidating
.
Example:
newtype Even = Even Int instance Validity Even validate (Event i) even i <?@> "The contained 'Int' is even." isValid (Event i) = even i
Semantics
isValid
should be an underapproximation of actual validity.
This means that if isValid
is not a perfect representation of actual
validity, for safety reasons, it should never return True
for invalid
values, but it may return False
for valid values.
For example:
isValid = const False
is a valid implementation for any type, because it never returns True
for invalid values.
isValid (Even i) = i == 2
is a valid implementation for newtype Even = Even Int
, but
isValid (Even i) = even i || i == 1
is not because it returns True
for an invalid value: '1'.
Automatic instances with Generic
An instance of this class can be made automatically if the type in question
has a Generic
instance. This instance will try to use isValid
to
on all structural sub-parts of the value that is being checked for validity.
Example:
{-# LANGUAGE DeriveGeneric #-} data MyType = MyType { myDouble :: Double { myString :: String } deriving (Show, Eq, Generic) instance Validity MyType
generates something like:
instance Validity MyType where isValid (MyType d s) = isValid d && isValid s validate (MyType d s) = d <?!> "myDouble" <> s <?!> "myString"
validate :: a -> Validation Source #
validate :: (Generic a, GValidity (Rep a)) => a -> Validation Source #
isValid :: (Generic a, GValidity (Rep a)) => a -> Bool Source #
Validity Bool Source # | Trivially valid |
Validity Char Source # | Trivially valid |
Validity Double Source # | NOT trivially valid:
|
Validity Float Source # | NOT trivially valid:
|
Validity Int Source # | Trivially valid |
Validity Integer Source # | Trivially valid Integer is not trivially valid under the hood, but instantiating
|
Validity Natural Source # | Valid according to Only available with |
Validity Ordering Source # | Trivially valid |
Validity Word Source # | Trivially valid |
Validity Word8 Source # | Trivially valid |
Validity Word16 Source # | Trivially valid |
Validity Word32 Source # | Trivially valid |
Validity Word64 Source # | Trivially valid |
Validity () Source # | Trivially valid |
Validity Validation Source # | |
Validity ValidationChain Source # | |
Validity a => Validity [a] Source # | A list of things is valid if all of the things are valid. This means that the empty list is considered valid.
If the empty list should not be considered valid as part of your custom data
type, make sure to write a custom |
Validity a => Validity (Maybe a) Source # | A Maybe thing is valid if the thing inside is valid or it's nothing
It makes sense to assume that |
(Num a, Ord a, Validity a) => Validity (Ratio a) Source # | Valid if the contained numbers are valid and the denominator is strictly positive. |
HasResolution a => Validity (Fixed a) Source # | Valid according to the contained |
Validity a => Validity (NonEmpty a) Source # | A nonempty list is valid if all the elements are valid. See the instance for 'Validity [a]' for more information. |
(Validity a, Validity b) => Validity (Either a b) Source # | Any Either of things is valid if the contents are valid in either of the cases. |
(Validity a, Validity b) => Validity (a, b) Source # | Any tuple of things is valid if both of its elements are valid |
(Validity a, Validity b, Validity c) => Validity (a, b, c) Source # | Any triple of things is valid if all three of its elements are valid |
(Validity a, Validity b, Validity c, Validity d) => Validity (a, b, c, d) Source # | Any quadruple of things is valid if all four of its elements are valid |
(Validity a, Validity b, Validity c, Validity d, Validity e) => Validity (a, b, c, d, e) Source # | Any quintuple of things is valid if all five of its elements are valid |
(Validity a, Validity b, Validity c, Validity d, Validity e, Validity f) => Validity (a, b, c, d, e, f) Source # | Any sextuple of things is valid if all six of its elements are valid |
Helper functions to define isValid
triviallyValid :: a -> Bool Source #
Declare any value to be valid.
triviallyValid a = seq a True
Helper functions to define validate
trivialValidation :: a -> Validation Source #
Declare any value to be valid in validation
trivialValidation a = seq a mempty
isValidByValidating :: Validity a => a -> Bool Source #
check :: Bool -> String -> Validation Source #
Check that a given invariant holds.
The given string should describe the invariant, not the violation.
Example:
check (x < 5) "x is strictly smaller than 5"
instead of
check (x < 5) "x is greater than 5"
(<?!>) :: Validity a => a -> String -> Validation infixr 0 Source #
Infix operator for annotate
Example:
validate (a, b) = mconcat [ a <?!> "The first element of the tuple" , b <?!> "The second element of the tuple" ]
annotate :: Validity a => a -> String -> Validation Source #
Declare a sub-part as a necessary part for validation, and annotate it with a name.
Example:
validate (a, b) = mconcat [ annotate a "The first element of the tuple" , annotate b "The second element of the tuple" ]
validateByChecking :: Validity a => String -> a -> Validation Source #
validateByCheckingName :: Validity a => String -> a -> Validation Source #
validateByCheckingDefault :: Validity a => a -> Validation Source #
Utilities
Utilities for validity checking
isInvalid :: Validity a => a -> Bool Source #
Check whether isInvalid
is not valid.
isInvalid = not . isValid
constructValid :: Validity a => a -> Maybe a Source #
Construct a valid element from an unchecked element
constructValidUnsafe :: (Show a, Validity a) => a -> a Source #
Construct a valid element from an unchecked element, throwing error
on invalid elements.
Utilities for validation
newtype Validation Source #
data ValidationChain Source #
checkValidity :: Validity a => a -> Either [ValidationChain] a Source #
validate a given value.
This function returns either all the reasons why the given value is invalid,
in the form of a list of ValidationChain
s, or it returns Right
with the
input value, as evidence that it is valid.
Note: You map want to use prettyValidation
instead, if you want to
display these ValidationChain
s to a user.
prettyValidation :: Validity a => a -> Either String a Source #
validate a given value, and return a nice error if the value is invalid.
Re-exports
The class of monoids (types with an associative binary operation that has an identity). Instances should satisfy the following laws:
mappend mempty x = x
mappend x mempty = x
mappend x (mappend y z) = mappend (mappend x y) z
mconcat =
foldr
mappend mempty
The method names refer to the monoid of lists under concatenation, but there are many other instances.
Some types can be viewed as a monoid in more than one way,
e.g. both addition and multiplication on numbers.
In such cases we often define newtype
s and make those instances
of Monoid
, e.g. Sum
and Product
.
Identity of mappend
An associative operation
Fold a list using the monoid.
For most types, the default definition for mconcat
will be
used, but the function is included in the class definition so
that an optimized version can be provided for specific types.
Monoid Ordering | Since: 2.1 |
Monoid () | Since: 2.1 |
Monoid All | Since: 2.1 |
Monoid Any | Since: 2.1 |
Monoid Validation # | |
Monoid [a] | Since: 2.1 |
Monoid a => Monoid (Maybe a) | Lift a semigroup into Since: 2.1 |
Monoid a => Monoid (IO a) | Since: 4.9.0.0 |
Monoid a => Monoid (Identity a) | |
Monoid a => Monoid (Dual a) | Since: 2.1 |
Monoid (Endo a) | Since: 2.1 |
Num a => Monoid (Sum a) | Since: 2.1 |
Num a => Monoid (Product a) | Since: 2.1 |
Monoid (First a) | Since: 2.1 |
Monoid (Last a) | Since: 2.1 |
Monoid b => Monoid (a -> b) | Since: 2.1 |
(Monoid a, Monoid b) => Monoid (a, b) | Since: 2.1 |
Monoid (Proxy k s) | Since: 4.7.0.0 |
(Monoid a, Monoid b, Monoid c) => Monoid (a, b, c) | Since: 2.1 |
Monoid a => Monoid (Const k a b) | |
Alternative f => Monoid (Alt * f a) | Since: 4.8.0.0 |
(Monoid a, Monoid b, Monoid c, Monoid d) => Monoid (a, b, c, d) | Since: 2.1 |
(Monoid a, Monoid b, Monoid c, Monoid d, Monoid e) => Monoid (a, b, c, d, e) | Since: 2.1 |