validity-0.5.0.0: Validity typeclass

Data.Validity

Description

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."

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).

Synopsis

# 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. It is a helper function that checks that validate says that there are no reasons why the value is invalid.

### Instantiating Validity

To instantiate Validity, one has to implement only validate. Use the helper functions below to define all the reasons why a given value would be a valid value of its type.

Example:

newtype Even = Even Int

instance Validity Even
validate (Event i)
even i <?@> "The contained 'Int' is even."

### 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 valid 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
validate (MyType d s)
= d <?!> "myDouble"
<> s <?!> "myString"

Methods

validate :: a -> Validation Source #

validate :: (Generic a, GValidity (Rep a)) => a -> Validation Source #

Instances

 Source # Trivially valid Methods Source # Trivially valid Methods Source # NOT trivially valid:NaN is not valid.Infinite values are not valid. Methods Source # NOT trivially valid:NaN is not valid.Infinite values are not valid. Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially validInteger is not trivially valid under the hood, but instantiating Validity correctly would force validity to depend on a specific (big integer library integer-gmp versus integer-simple). This is rather impractical so for the time being we have opted for assuming that an Integer is always valid. Even though this is not technically sound, it is good enough for now. Methods Source # Valid according to isValidNaturalOnly available with base >= 4.8. Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially valid Methods Source # Trivially valid Methods Validity () Source # Trivially valid Methodsvalidate :: () -> Validation Source # Source # Methods Source # Methods 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 instance Methodsvalidate :: [a] -> Validation Source # 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 Nothing is valid. If Nothing wasn't valid, you wouldn't have used a Maybe in the datastructure. Methods (Num a, Ord a, Validity a) => Validity (Ratio a) Source # Valid if the contained numbers are valid and the denominator is strictly positive. Methods HasResolution a => Validity (Fixed a) Source # Valid according to the contained Integer. Methods 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. Methods (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. Methodsvalidate :: Either a b -> Validation Source # (Validity a, Validity b) => Validity (a, b) Source # Any tuple of things is valid if both of its elements are valid Methodsvalidate :: (a, b) -> Validation Source # (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 Methodsvalidate :: (a, b, c) -> Validation Source # (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 Methodsvalidate :: (a, b, c, d) -> Validation Source # (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 Methodsvalidate :: (a, b, c, d, e) -> Validation Source # (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 Methodsvalidate :: (a, b, c, d, e, f) -> Validation Source #

# Helper functions to define validate

Declare any value to be valid in validation

trivialValidation a = seq a mempty

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"

check (x < 5) "x is greater than 5"

check, but with the arguments flipped

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"
]

delve :: Validity a => String -> a -> Validation Source #

annotate, but with the arguments flipped.

Construct a trivially invalid Validation

Example:

data Wrong
= Wrong
| Fine
deriving (Show, Eq)

instance Validity Wrong where
validate w =
case w of
Wrong -> invalid "Wrong"
Fine -> valid

# 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 #

Constructors

 Validation FieldsunValidation :: [ValidationChain]

Instances

 Source # Methods Source # MethodsshowList :: [Validation] -> ShowS # Source # Associated Typestype Rep Validation :: * -> * # Methods Source # Methodsmconcat :: [Validation] -> Validation # Source # Methods type Rep Validation Source # type Rep Validation = D1 * (MetaData "Validation" "Data.Validity" "validity-0.5.0.0-KUXfF57kEp8ANLGKT9YxF9" True) (C1 * (MetaCons "Validation" PrefixI True) (S1 * (MetaSel (Just Symbol "unValidation") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 * [ValidationChain])))

Constructors

 Violated String Location String ValidationChain

Instances

 Source # Methods Source # MethodsshowList :: [ValidationChain] -> ShowS # Source # Associated Typestype Rep ValidationChain :: * -> * # Methods Source # Methods Source # type Rep ValidationChain = D1 * (MetaData "ValidationChain" "Data.Validity" "validity-0.5.0.0-KUXfF57kEp8ANLGKT9YxF9" False) ((:+:) * (C1 * (MetaCons "Violated" PrefixI False) (S1 * (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 * String))) (C1 * (MetaCons "Location" PrefixI False) ((:*:) * (S1 * (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 * String)) (S1 * (MetaSel (Nothing Symbol) NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 * ValidationChain)))))

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 ValidationChains, 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 ValidationChains 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

class Monoid a where #

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 newtypes and make those instances of Monoid, e.g. Sum and Product.

Minimal complete definition

Methods

mempty :: a #

Identity of mappend

mappend :: a -> a -> a #

An associative operation

mconcat :: [a] -> a #

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.

Instances

 Since: 2.1 Methodsmconcat :: [Ordering] -> Ordering # Monoid () Since: 2.1 Methodsmempty :: () #mappend :: () -> () -> () #mconcat :: [()] -> () # Since: 2.1 Methodsmappend :: All -> All -> All #mconcat :: [All] -> All # Since: 2.1 Methodsmappend :: Any -> Any -> Any #mconcat :: [Any] -> Any # # Methodsmconcat :: [Validation] -> Validation # Monoid [a] Since: 2.1 Methodsmempty :: [a] #mappend :: [a] -> [a] -> [a] #mconcat :: [[a]] -> [a] # Monoid a => Monoid (Maybe a) Lift a semigroup into Maybe forming a Monoid according to http://en.wikipedia.org/wiki/Monoid: "Any semigroup S may be turned into a monoid simply by adjoining an element e not in S and defining e*e = e and e*s = s = s*e for all s ∈ S." Since there used to be no "Semigroup" typeclass providing just mappend, we use Monoid instead.Since: 2.1 Methodsmempty :: Maybe a #mappend :: Maybe a -> Maybe a -> Maybe a #mconcat :: [Maybe a] -> Maybe a # Monoid a => Monoid (IO a) Since: 4.9.0.0 Methodsmempty :: IO a #mappend :: IO a -> IO a -> IO a #mconcat :: [IO a] -> IO a # Monoid a => Monoid (Identity a) Methodsmappend :: Identity a -> Identity a -> Identity a #mconcat :: [Identity a] -> Identity a # Monoid a => Monoid (Dual a) Since: 2.1 Methodsmempty :: Dual a #mappend :: Dual a -> Dual a -> Dual a #mconcat :: [Dual a] -> Dual a # Monoid (Endo a) Since: 2.1 Methodsmempty :: Endo a #mappend :: Endo a -> Endo a -> Endo a #mconcat :: [Endo a] -> Endo a # Num a => Monoid (Sum a) Since: 2.1 Methodsmempty :: Sum a #mappend :: Sum a -> Sum a -> Sum a #mconcat :: [Sum a] -> Sum a # Num a => Monoid (Product a) Since: 2.1 Methodsmappend :: Product a -> Product a -> Product a #mconcat :: [Product a] -> Product a # Monoid (First a) Since: 2.1 Methodsmempty :: First a #mappend :: First a -> First a -> First a #mconcat :: [First a] -> First a # Monoid (Last a) Since: 2.1 Methodsmempty :: Last a #mappend :: Last a -> Last a -> Last a #mconcat :: [Last a] -> Last a # Monoid b => Monoid (a -> b) Since: 2.1 Methodsmempty :: a -> b #mappend :: (a -> b) -> (a -> b) -> a -> b #mconcat :: [a -> b] -> a -> b # (Monoid a, Monoid b) => Monoid (a, b) Since: 2.1 Methodsmempty :: (a, b) #mappend :: (a, b) -> (a, b) -> (a, b) #mconcat :: [(a, b)] -> (a, b) # Monoid (Proxy k s) Since: 4.7.0.0 Methodsmempty :: Proxy k s #mappend :: Proxy k s -> Proxy k s -> Proxy k s #mconcat :: [Proxy k s] -> Proxy k s # (Monoid a, Monoid b, Monoid c) => Monoid (a, b, c) Since: 2.1 Methodsmempty :: (a, b, c) #mappend :: (a, b, c) -> (a, b, c) -> (a, b, c) #mconcat :: [(a, b, c)] -> (a, b, c) # Monoid a => Monoid (Const k a b) Methodsmempty :: Const k a b #mappend :: Const k a b -> Const k a b -> Const k a b #mconcat :: [Const k a b] -> Const k a b # Alternative f => Monoid (Alt * f a) Since: 4.8.0.0 Methodsmempty :: Alt * f a #mappend :: Alt * f a -> Alt * f a -> Alt * f a #mconcat :: [Alt * f a] -> Alt * f a # (Monoid a, Monoid b, Monoid c, Monoid d) => Monoid (a, b, c, d) Since: 2.1 Methodsmempty :: (a, b, c, d) #mappend :: (a, b, c, d) -> (a, b, c, d) -> (a, b, c, d) #mconcat :: [(a, b, c, d)] -> (a, b, c, d) # (Monoid a, Monoid b, Monoid c, Monoid d, Monoid e) => Monoid (a, b, c, d, e) Since: 2.1 Methodsmempty :: (a, b, c, d, e) #mappend :: (a, b, c, d, e) -> (a, b, c, d, e) -> (a, b, c, d, e) #mconcat :: [(a, b, c, d, e)] -> (a, b, c, d, e) #