validity-0.3.3.0: Validity typeclass

Safe HaskellNone
LanguageHaskell2010

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

Synopsis

Documentation

class Validity a where #

A class of types that have additional invariants defined upon them that aren't enforced by the type system

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

Methods

isValid :: a -> Bool #

isValid :: (Generic a, GValidity (Rep a)) => a -> Bool #

Instances

Validity Bool #

Trivially valid

Methods

isValid :: Bool -> Bool #

Validity Char #

Trivially valid

Methods

isValid :: Char -> Bool #

Validity Double #

NOT trivially valid:

  • NaN is not valid.
  • Infinite values are not valid.

Methods

isValid :: Double -> Bool #

Validity Float #

NOT trivially valid:

  • NaN is not valid.
  • Infinite values are not valid.

Methods

isValid :: Float -> Bool #

Validity Int #

Trivially valid

Methods

isValid :: Int -> Bool #

Validity Integer #

Trivially valid

Integer 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

isValid :: Integer -> Bool #

Validity Ordering #

Trivially valid

Methods

isValid :: Ordering -> Bool #

Validity Rational #

Valid if the contained Integers are valid and the denominator is strictly positive.

Methods

isValid :: Rational -> Bool #

Validity Word #

Trivially valid

Methods

isValid :: Word -> Bool #

Validity Word8 #

Trivially valid

Methods

isValid :: Word8 -> Bool #

Validity Word16 #

Trivially valid

Methods

isValid :: Word16 -> Bool #

Validity () #

Trivially valid

Methods

isValid :: () -> Bool #

Validity Natural #

Valid according to isValidNatural

Only available with base >= 4.8.

Methods

isValid :: Natural -> Bool #

Validity a => Validity [a] #

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

Methods

isValid :: [a] -> Bool #

Validity a => Validity (Maybe a) #

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

isValid :: Maybe a -> Bool #

HasResolution a => Validity (Fixed a) #

Valid according to the contained Integer.

Methods

isValid :: Fixed a -> Bool #

(Validity a, Validity b) => Validity (Either a b) #

Any Either of things is valid if the contents are valid in either of the cases.

Methods

isValid :: Either a b -> Bool #

(Validity a, Validity b) => Validity (a, b) #

Any tuple of things is valid if both of its elements are valid

Methods

isValid :: (a, b) -> Bool #

(Validity a, Validity b, Validity c) => Validity (a, b, c) #

Any tuple of things is valid if all three of its elements are valid

Methods

isValid :: (a, b, c) -> Bool #

isInvalid :: Validity a => a -> Bool #

constructValid :: Validity a => a -> Maybe a #

Construct a valid element from an unchecked element

constructValidUnsafe :: (Show a, Validity a) => a -> a #

Construct a valid element from an unchecked element, throwing error on invalid elements.