-- | -- Module : Test.LeanCheck.Utils.Operators -- Copyright : (c) 2015-2020 Rudy Matela -- License : 3-Clause BSD (see the file LICENSE) -- Maintainer : Rudy Matela -- -- This module is part of LeanCheck, -- a simple enumerative property-based testing library. -- -- Some operators for property-based testing. module Test.LeanCheck.Utils.Operators ( -- * Combining properties (==>) , (===), (====) , (&&&), (&&&&), (&&&&&) , (|||), (||||) -- * Properties of unary functions , isIdempotent , isIdentity , isNeverIdentity -- * Properties of operators (binary functions) , isCommutative , isAssociative , isDistributiveOver , isLeftDistributiveOver , isRightDistributiveOver , isFlipped -- * Properties of relations (binary functions returning truth values) , isTransitive , isReflexive , isIrreflexive , isSymmetric , isAsymmetric , isAntisymmetric -- * Properties of order relations , isEquivalence , isPartialOrder , isStrictPartialOrder , isTotalOrder , isStrictTotalOrder , isComparison -- * Ternary comparison operators , (=$), ($=) , (=|), (|=) -- * Properties for typeclass instances , okEq , okOrd , okEqOrd , okNum , okNumNonNegative -- * Deprecated functions , idempotent , identity , neverIdentity , commutative , associative , distributive , symmetric2 , transitive , reflexive , irreflexive , symmetric , asymmetric , antisymmetric , equivalence , partialOrder , strictPartialOrder , totalOrder , strictTotalOrder , comparison ) where import Test.LeanCheck ((==>)) combine :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d) combine (?) f g = \x -> f x ? g x -- Uneeded, just food for thought: -- > combine2 :: (c -> d -> e) -> (a -> b -> c) -> (a -> b -> d) -> (a -> b -> e) -- Two possible implementations: -- > combine2 op f g = \x y -> f x y `op` g x y -- > combine2 = combine . combine -- | Allows building equality properties between functions. -- -- > prop_id_idempotent = id === id . id -- -- > > check $ id === (+0) -- > +++ OK, passed 200 tests. -- -- > > check $ id === id . id -- > +++ OK, passed 1 tests (exhausted). -- -- > > check $ id === (+1) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 (===) :: Eq b => (a -> b) -> (a -> b) -> a -> Bool (===) = combine (==) infix 4 === -- | Allows building equality properties between two-argument functions. -- -- > > holds 100 $ const ==== asTypeOf -- > True -- -- > > holds 100 $ (+) ==== flip (+) -- > True -- -- > > holds 100 $ (+) ==== (*) -- > False (====) :: Eq c => (a -> b -> c) -> (a -> b -> c) -> a -> b -> Bool (====) = combine (===) infix 4 ==== -- | And ('&&') operator over one-argument properties. -- -- Allows building conjuntions between one-argument properties: -- -- > > holds 100 $ id === (+0) &&& id === (id . id) -- > True (&&&) :: (a -> Bool) -> (a -> Bool) -> a -> Bool (&&&) = combine (&&) infixr 3 &&& -- | And ('&&') operator over two-argument properties. -- -- Allows building conjuntions between two-argument properties: -- -- > > holds 100 $ (+) ==== flip (+) &&&& (+) ==== (*) -- > False (&&&&) :: (a -> b -> Bool) -> (a -> b -> Bool) -> a -> b -> Bool (&&&&) = combine (&&&) infixr 3 &&&& -- | And operator over three-argument properties. (&&&&&) :: (a -> b -> c -> Bool) -> (a -> b -> c -> Bool) -> a -> b -> c -> Bool (&&&&&) = combine (&&&&) infixr 3 &&&&& -- | Or ('||') operator over one-argument properties. -- -- Allows building disjunctions between one-argument properties: -- -- > > holds 100 $ id === (+0) ||| id === (id . id) -- > True (|||) :: (a -> Bool) -> (a -> Bool) -> a -> Bool (|||) = combine (||) infixr 2 ||| -- | Or ('||') operator over two-argument properties. -- -- Allows building conjuntions between two-argument properties: -- -- > > holds 100 $ (+) ==== flip (+) |||| (+) ==== (*) -- > True (||||) :: (a -> b -> Bool) -> (a -> b -> Bool) -> a -> b -> Bool (||||) = combine (|||) infixr 2 |||| -- | Is a given operator commutative? @x + y = y + x@ -- -- > > check $ isCommutative (+) -- > +++ OK, passed 200 tests. -- -- > > import Data.List -- > > check $ isCommutative (union :: [Int]->[Int]->[Int]) -- > *** Failed! Falsifiable (after 4 tests): -- > [] [0,0] isCommutative :: Eq b => (a -> a -> b) -> a -> a -> Bool isCommutative (?) = \x y -> x ? y == y ? x -- | Is a given operator associative? @x + (y + z) = (x + y) + z@ -- -- > > check $ isAssociative (+) -- > +++ OK, passed 200 tests. -- -- > > check $ isAssociative (-) -- > *** Failed! Falsifiable (after 2 tests): -- > 0 0 1 isAssociative :: Eq a => (a -> a -> a) -> a -> a -> a -> Bool isAssociative (?) = \x y z -> x ? (y ? z) == (x ? y) ? z -- | Does the first operator, left-distributes over the second? -- -- This is an alias to 'isLeftDistributiveOver'. isDistributiveOver :: Eq a => (a -> a -> a) -> (a -> a -> a) -> a -> a -> a -> Bool isDistributiveOver = isLeftDistributiveOver -- | Does the first operator, left-distributes over the second? -- @x * (y + z) = (x * y) + (x * z)@ -- -- > > check $ (*) `isLeftDistributiveOver` (+) -- > +++ OK, passed 200 tests. -- -- > > check $ (+) `isLeftDistributiveOver` (*) -- > *** Failed! Falsifiable (after 8 tests): -- > 1 0 1 isLeftDistributiveOver :: Eq a => (a -> a -> a) -> (a -> a -> a) -> a -> a -> a -> Bool (?) `isLeftDistributiveOver` (#) = \x y z -> x ? (y # z) == (x ? y) # (x ? z) -- | Does the first operator, right-distributes over the second? -- @(y + z) * x = (y * x) + (z * x)@ -- -- > > check $ (*) `isRightDistributiveOver` (+) -- > +++ OK, passed 200 tests. -- -- > > check $ (+) `isRightDistributiveOver` (*) -- > *** Failed! Falsifiable (after 8 tests): -- > 1 0 1 isRightDistributiveOver :: Eq a => (a -> a -> a) -> (a -> a -> a) -> a -> a -> a -> Bool (?) `isRightDistributiveOver` (#) = \x y z -> (y # z) ? x == (y ? x) # (z ? x) -- | Are two operators 'flip'ped versions of each other? -- -- > > check $ ((<) `isFlipped` (>) :: Int -> Int -> Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ ((<=) `isFlipped` (>=) :: Int -> Int -> Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ ((<) `isFlipped` (>=) :: Int -> Int -> Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 0 -- -- > > check $ ((<=) `isFlipped` (>) :: Int -> Int -> Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 0 isFlipped :: Eq c => (a -> b -> c) -> (b -> a -> c) -> a -> b -> Bool (+-) `isFlipped` (-+) = \x y -> x +- y == y -+ x -- | Is a given relation transitive? -- -- A relation is transitive when -- if a is related to b then b is related to c. -- -- > > check $ isTransitive ((==) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ isTransitive ((/=) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 3 tests): -- > 0 1 0 isTransitive :: (a -> a -> Bool) -> a -> a -> a -> Bool isTransitive (?) = \x y z -> x ? y && y ? z ==> x ? z -- | Is a given relation reflexive? -- -- A relation is reflexive when -- an element is always related to itself. -- -- > > check $ isReflexive ((==) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ isReflexive ((/=) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 isReflexive :: (a -> a -> Bool) -> a -> Bool isReflexive (?) = \x -> x ? x -- | Is a given relation irreflexive? -- -- A given relation is irreflexive or anti-reflexive -- when an element is _never_ related to itself. -- -- This is /not/ the negation of 'isReflexive'. -- -- > > check $ isIrreflexive ((==) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 -- -- > > check $ isIrreflexive ((/=) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. isIrreflexive :: (a -> a -> Bool) -> a -> Bool isIrreflexive (?) = \x -> not $ x ? x -- | Is a given relation symmetric? -- -- A relation is symmetric when -- if a is related to b, then b is related to a. -- -- > > check $ isSymmetric (&&) -- > +++ OK, passed 4 tests (exhausted). -- -- > > check $ isSymmetric (==>) -- > *** Failed! Falsifiable (after 2 tests): -- > False True -- -- This is a type-restricted version of 'isCommutative'. isSymmetric :: (a -> a -> Bool) -> a -> a -> Bool isSymmetric = isCommutative -- | Is a given relation antisymmetric? -- -- Not to be confused with 'isAsymmetric'. -- Not to be confused with the negation of 'isSymmetric'. -- -- > > check $ isAntisymmetric ((<=) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ isAntisymmetric ((/=) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 2 tests): -- > 0 1 isAntisymmetric :: Eq a => (a -> a -> Bool) -> a -> a -> Bool isAntisymmetric (?) = \x y -> x ? y && y ? x ==> x == y -- | Is a given relation asymmetric? -- -- Not to be confused with not 'isSymmetric' and 'isAntisymmetric'. -- -- > > check $ isAsymmetric ((<=) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 0 -- -- > > check $ isAsymmetric ((<) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. isAsymmetric :: (a -> a -> Bool) -> a -> a -> Bool isAsymmetric (?) = \x y -> x ? y ==> not (y ? x) -- | Is the given binary relation an equivalence? -- -- In other words, -- is the given relation reflexive, symmetric and transitive? -- -- > > check (isEquivalence (==) :: Int -> Int -> Int -> Bool) -- > +++ OK, passed 200 tests. -- -- > > check (isEquivalence (<=) :: Int -> Int -> Int -> Bool) -- > *** Failed! Falsifiable (after 3 tests): -- > 0 1 0 -- -- Or, using "Test.LeanCheck.Utils.TypeBinding": -- -- > > check $ isEquivalence (<=) -:> int -- > *** Failed! Falsifiable (after 3 tests): -- > 0 1 0 isEquivalence :: (a -> a -> Bool) -> a -> a -> a -> Bool isEquivalence (==) = \x y z -> isReflexive (==) x && isSymmetric (==) x y && isTransitive (==) x y z -- | Is the given binary relation a partial order? -- -- In other words, -- is the given relation reflexive, antisymmetric and transitive? -- -- > > check $ isPartialOrder ((<) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 0 0 -- -- > > check $ isPartialOrder ((<=) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ isPartialOrder isSubsetOf -- > +++ OK, passed 200 tests. isPartialOrder :: Eq a => (a -> a -> Bool) -> a -> a -> a -> Bool isPartialOrder (<=) = \x y z -> isReflexive (<=) x && isAntisymmetric (<=) x y && isTransitive (<=) x y z -- | Is the given binary relation a strict partial order? -- -- In other words, -- is the given relation irreflexive, asymmetric and transitive? -- -- > > check $ isStrictPartialOrder ((<) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ isStrictPartialOrder ((<=) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 0 0 isStrictPartialOrder :: (a -> a -> Bool) -> a -> a -> a -> Bool isStrictPartialOrder (<) = \x y z -> isIrreflexive (<) x && isAsymmetric (<) x y -- implied? && isTransitive (<) x y z -- | Is the given binary relation a total order? -- -- > > check $ isTotalOrder ((<) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 0 0 -- > > check $ isTotalOrder ((<=) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. isTotalOrder :: Eq a => (a -> a -> Bool) -> a -> a -> a -> Bool isTotalOrder (<=) = \x y z -> (x <= y || y <= x) && isAntisymmetric (<=) x y && isTransitive (<=) x y z -- | Is the given binary relation a strict total order? -- -- > > check $ isStrictTotalOrder ((<=) :: Int->Int->Bool) -- > *** Failed! Falsifiable (after 1 tests): -- > 0 0 0 -- -- > > check $ isStrictTotalOrder ((<) :: Int->Int->Bool) -- > +++ OK, passed 200 tests. isStrictTotalOrder :: Eq a => (a -> a -> Bool) -> a -> a -> a -> Bool isStrictTotalOrder (<) = \x y z -> (x /= y ==> x < y || y < x) && isIrreflexive (<) x && isAsymmetric (<) x y -- implied? && isTransitive (<) x y z -- | Does the given 'compare' function follow the required properties? -- -- This is useful for testing custom 'Ord' instances. -- -- > > check $ isComparison (compare :: Int->Int->Ordering) -- > +++ OK, passed 200 tests. isComparison :: (a -> a -> Ordering) -> a -> a -> a -> Bool isComparison compare = \x y z -> isEquivalence (===) x y z && isIrreflexive (<) x && isTransitive (<) x y z && ((<) `isFlipped` (>)) x y where x === y = x `compare` y == EQ x < y = x `compare` y == LT x > y = x `compare` y == GT -- | Is the given function idempotent? @f (f x) == x@ -- -- > > check $ isIdempotent abs -- > +++ OK, passed 200 tests. -- -- > > check $ isIdempotent sort -- > +++ OK, passed 200 tests. -- -- > > check $ isIdempotent negate -- > *** Failed! Falsifiable (after 2 tests): -- > 1 isIdempotent :: Eq a => (a -> a) -> a -> Bool isIdempotent f = f . f === f -- | Is the given function an identity? @f x == x@ -- -- > > check $ isIdentity (+0) -- > +++ OK, passed 200 tests. -- -- > > check $ isIdentity (sort :: [()]->[()]) -- > +++ OK, passed 200 tests. -- -- > > check $ isIdentity (not . not) -- > +++ OK, passed 2 tests (exhausted). isIdentity :: Eq a => (a -> a) -> a -> Bool isIdentity f = f === id -- | Is the given function never an identity? @f x /= x@ -- -- > > check $ neverIdentity not -- > +++ OK, passed 2 tests (exhausted). -- -- > > check $ neverIdentity negate -- > *** Failed! Falsifiable (after 1 tests): -- > 0 -- -- Note: this is not the same as not being an 'identity'. isNeverIdentity :: Eq a => (a -> a) -> a -> Bool isNeverIdentity = (not .) . isIdentity -- | Is this 'Eq' instance valid? -- -- This is useful for testing your custom 'Eq' instances -- against required properties. -- -- In particular, -- this function tests that '==' is an equivalence -- and that '/=' is the negation of '=='. -- -- > > check $ (okEq :: Int -> Int -> Int -> Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ (okEq :: Bool -> Bool -> Bool -> Bool) -- > +++ OK, passed 8 tests (exhausted). okEq :: Eq a => a -> a -> a -> Bool okEq x y z = isEquivalence (==) x y z && (x /= y) == not (x == y) -- | Is this 'Ord' instance valid? -- -- This is useful for testing your custom 'Ord' instances -- against required properties. -- -- > > check $ (okOrd :: Int -> Int -> Int -> Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ (okOrd :: Bool -> Bool -> Bool -> Bool) -- > +++ OK, passed 8 tests (exhausted). okOrd :: Ord a => a -> a -> a -> Bool okOrd x y z = isTotalOrder (<=) x y z && isComparison compare x y z && (x <= y) == ((x `compare` y) `elem` [LT,EQ]) -- | Is this 'Eq' and 'Ord' instance valid and consistent? -- -- This is useful for testing your custom 'Eq' and 'Ord' instances -- against required properties. -- -- > > check $ (okEqOrd :: Int -> Int -> Int -> Bool) -- > +++ OK, passed 200 tests. -- -- > > check $ (okEqOrd :: Bool -> Bool -> Bool -> Bool) -- > +++ OK, passed 8 tests (exhausted). okEqOrd :: (Eq a, Ord a) => a -> a -> a -> Bool okEqOrd x y z = okEq x y z && okOrd x y z && (x == y) == (x `compare` y == EQ) -- consistent instances -- | Like 'okNum' but restricted to zero and positives. -- -- > > check (okNumNonNegative :: Natural -> Natural -> Natural -> Bool) -- > +++ OK, passed 200 tests. okNumNonNegative :: (Eq a, Num a) => a -> a -> a -> Bool okNumNonNegative x y z = isCommutative (+) x y && isCommutative (*) x y && isAssociative (+) x y z && isAssociative (*) x y z && ((*) `isDistributiveOver` (+)) x y z && isIdempotent (+0) x && isIdempotent (*1) x && isIdempotent abs x && isIdempotent signum x && abs x * signum x == x -- | Is this 'Num' instance valid? -- -- This is useful for testing your custom 'Num' instances -- against required properties. -- -- > > check (okNum :: Int -> Int -> Int -> Bool) -- > +++ OK, passed 200 tests. -- -- Double is /mostly/ valid, but not /entirely/ valid: -- -- > > check (okNum :: Double -> Double -> Double -> Bool) -- > *** Failed! Falsifiable (after 6 tests): -- 0.0 0.0 Infinity okNum :: (Eq a, Num a) => a -> a -> a -> Bool okNum x y z = okNumNonNegative x y z && negate (negate x) == x && x - x == 0 -- | Equal under, a ternary operator with the same fixity as '=='. -- -- > x =$ f $= y = f x == f y -- -- > > [1,2,3,4,5] =$ take 2 $= [1,2,4,8,16] -- > True -- -- > > [1,2,3,4,5] =$ take 3 $= [1,2,4,8,16] -- > False -- -- > > [1,2,3] =$ sort $= [3,2,1] -- > True -- -- > > 42 =$ (`mod` 10) $= 16842 -- > True -- -- > > 42 =$ (`mod` 9) $= 16842 -- > False -- -- > > 'a' =$ isLetter $= 'b' -- > True -- -- > > 'a' =$ isLetter $= '1' -- > False (=$) :: Eq b => a -> (a -> b) -> a -> Bool (x =$ f) y = f x == f y infixl 4 =$ -- | See '=$' ($=) :: (a -> Bool) -> a -> Bool ($=) = ($) infixl 4 $= -- | Check if two lists are equal for @n@ values. -- This operator has the same fixity of '=='. -- -- > xs =| n |= ys = take n xs == take n ys -- -- > [1,2,3,4,5] =| 2 |= [1,2,4,8,16] -- > True -- > [1,2,3,4,5] =| 3 |= [1,2,4,8,16] -- > False (=|) :: Eq a => [a] -> Int -> [a] -> Bool xs =| n = xs =$ take n infixl 4 =| -- | See '=|' (|=) :: (a -> Bool) -> a -> Bool (|=) = ($) infixl 4 |= -- | Deprecated: use 'isCommutative'. {-# DEPRECATED commutative "Use isCommutative." #-} commutative :: Eq b => (a -> a -> b) -> a -> a -> Bool commutative = isCommutative -- | Deprecated: use 'isAssociative'. {-# DEPRECATED associative "Use isAssociative." #-} associative :: Eq a => (a -> a -> a) -> a -> a -> a -> Bool associative = isAssociative -- | Deprecated: use 'isDistributiveOver'. {-# DEPRECATED distributive "Use isDistributiveOver." #-} distributive :: Eq a => (a -> a -> a) -> (a -> a -> a) -> a -> a -> a -> Bool distributive = isDistributiveOver -- | Deprecated: use 'isFlipped'. {-# DEPRECATED symmetric2 "Use isFlipped." #-} symmetric2 :: Eq c => (a -> b -> c) -> (b -> a -> c) -> a -> b -> Bool symmetric2 = isFlipped -- | Deprecated: use 'isTransitive'. {-# DEPRECATED transitive "Use isTransitive." #-} transitive :: (a -> a -> Bool) -> a -> a -> a -> Bool transitive = isTransitive -- | Deprecated: use 'isReflexive'. {-# DEPRECATED reflexive "Use isReflexive." #-} reflexive :: (a -> a -> Bool) -> a -> Bool reflexive = isReflexive -- | Deprecated: use 'isIrreflexive'. {-# DEPRECATED irreflexive "Use isIrreflexive." #-} irreflexive :: (a -> a -> Bool) -> a -> Bool irreflexive = isIrreflexive -- | Deprecated: use 'isSymmetric'. {-# DEPRECATED symmetric "Use isSymmetric." #-} symmetric :: (a -> a -> Bool) -> a -> a -> Bool symmetric = isSymmetric -- | Deprecated: use 'isAntisymmetric'. {-# DEPRECATED antisymmetric "Use isAntisymmetric." #-} antisymmetric :: Eq a => (a -> a -> Bool) -> a -> a -> Bool antisymmetric = isAntisymmetric -- | Deprecated: use 'isAsymmetric'. {-# DEPRECATED asymmetric "Use isAsymmetric." #-} asymmetric :: (a -> a -> Bool) -> a -> a -> Bool asymmetric = isAsymmetric -- | Deprecated: use 'isEquivalence'. {-# DEPRECATED equivalence "Use isEquivalence." #-} equivalence :: (a -> a -> Bool) -> a -> a -> a -> Bool equivalence = isEquivalence -- | Deprecated: use 'isPartialOrder'. {-# DEPRECATED partialOrder "Use isPartialOrder." #-} partialOrder :: Eq a => (a -> a -> Bool) -> a -> a -> a -> Bool partialOrder = isPartialOrder -- | Deprecated: use 'isStrictPartialOrder'. {-# DEPRECATED strictPartialOrder "Use isStrictPartialOrder." #-} strictPartialOrder :: (a -> a -> Bool) -> a -> a -> a -> Bool strictPartialOrder = isStrictPartialOrder -- | Deprecated: use 'isTotalOrder'. {-# DEPRECATED totalOrder "Use isTotalOrder." #-} totalOrder :: Eq a => (a -> a -> Bool) -> a -> a -> a -> Bool totalOrder = isTotalOrder -- | Deprecated: use 'isStrictTotalOrder'. {-# DEPRECATED strictTotalOrder "Use isStrictTotalOrder." #-} strictTotalOrder :: Eq a => (a -> a -> Bool) -> a -> a -> a -> Bool strictTotalOrder = isStrictTotalOrder -- | Deprecated: use 'isComparison'. {-# DEPRECATED comparison "Use isComparison." #-} comparison :: (a -> a -> Ordering) -> a -> a -> a -> Bool comparison = isComparison -- | Deprecated: use 'isIdempotent'. {-# DEPRECATED idempotent "Use isIdempotent." #-} idempotent :: Eq a => (a -> a) -> a -> Bool idempotent = isIdempotent -- | Deprecated: use 'isIdentity'. {-# DEPRECATED identity "Use isIdentity." #-} identity :: Eq a => (a -> a) -> a -> Bool identity = isIdentity -- | Deprecated: use 'isNeverIdentity'. {-# DEPRECATED neverIdentity "Use isNeverIdentity." #-} neverIdentity :: Eq a => (a -> a) -> a -> Bool neverIdentity = isNeverIdentity