-- | Functions to build 'TestTree' that test properties of -- typeclasses, such as the functor laws, monad laws, and monoid laws. -- These functions are rough; for example, they do not shrink on -- failure, they are monomorphic, and they do not show the -- counterexamples of failing functions. But they are sufficient to -- help verify the lawfulness of your types. module Quickpull.Laws where import Control.Applicative import Data.Monoid import Quickpull.Types import Test.QuickCheck -- | Tests the monad laws: -- -- Left identity: -- -- > return a >>= f == f a -- -- Right identity: -- -- > m >>= return == m -- -- Associativity: -- -- > (m >>= f) >>= g == m >>= (\x -> f x >>= g) monad :: (Eq b, Show b, Monad m) => Gen (m Int) -- ^ Generates a computation in the monad. -> Gen (Int -> m Int) -- ^ Generates a function that, when applied to an Int, returns a -- computation in the monad. -> Gen (m Int -> b) -- ^ Generates a function that runs a computation in the monad. -> TestTree monad genK genF genU = group "monad laws" [ test "left identity" monadLeftIdentity , test "right identity" monadRightIdentity , test "associativity" monadAssociativity ] where monadLeftIdentity = do i <- arbitrary f <- genF u <- genU return $ (u (return i >>= f)) === u (f i) monadRightIdentity = do m <- genK u <- genU return $ (u (m >>= return)) === u m monadAssociativity = do m <- genK f <- genF g <- genF u <- genU return $ (u ((m >>= f) >>= g)) === (u (m >>= (\x -> f x >>= g))) -- | Tests the functor laws: -- -- > fmap id == id -- -- > fmap (f . g) == fmap f . fmap g functor :: (Eq b, Show b, Functor f) => Gen (f Int) -- ^ Generates a computation in the functor. -> Gen (f Int -> b) -- ^ Generates a computation that unwraps the functor. -> TestTree functor genK genU = group "functor laws" [ test "identity" tIdentity , test "composition" tComposition ] where tIdentity = do k <- genK u <- genU return $ (u (fmap id k)) === (u (id k)) tComposition = do k <- genK u <- genU f <- arbitrary let _types = f :: Int -> Int g <- arbitrary return $ (u (fmap (f . g) k)) === (u ((fmap f . fmap g) k)) -- | Tests the Applicative laws: -- -- * identity: -- -- > pure id <*> v == v -- -- * composition: -- -- > pure (.) <*> u <*> v <*> w == u <*> (v <*> w) -- -- * homomorphism: -- -- > pure f <*> pure x = pure (f x) -- -- * interchange: -- -- > u <*> pure y = pure ($ y) <*> u applicative :: (Eq b, Show b, Applicative f) => Gen (f Int) -- ^ Generates a computation in the Applicative. -> Gen (f (Int -> Int)) -- ^ Generates a function in the Applicative. -> Gen (f Int -> b) -- ^ Generates an unwrapping function. -> TestTree applicative gK gF gU = group "applicative laws" [ test "identity" tIdentity , test "composition" tComposition , test "homomorphism" tHomomorphism , test "interchange" tInterchange ] where tIdentity = do u <- gU v <- gK return $ (u (pure id <*> v)) === (u v) tComposition = do u <- gF v <- gF w <- gK r <- gU return $ (r (pure (.) <*> u <*> v <*> w)) === (r (u <*> (v <*> w))) tHomomorphism = do f <- arbitrary let _types = f :: Int -> Int x <- arbitrary u <- gU return $ (u (pure f <*> pure x)) === (u (pure (f x))) tInterchange = do r <- gU u <- gF y <- arbitrary return $ (r (u <*> pure y)) === (r (pure ($ y) <*> u)) -- | Tests the monoid laws: -- -- > mappend mempty x = x -- -- > mappend x mempty = x -- -- > mappend x (mappend y z) = mappend (mappend x y) z -- -- > mconcat = foldr mappend mempty monoid :: (Eq b, Show b, Monoid a) => Gen a -- ^ Generates monoid values -> Gen (a -> b) -- ^ Generates unwrappers -> TestTree monoid gV gU = group "monoid laws" [ test "left identity" tLeft , test "right identity" tRight , test "associativity" tAssociative , test "mconcat = foldr" tFoldr ] where tLeft = do x <- gV u <- gU return $ (u (mappend mempty x)) === (u x) tRight = do x <- gV u <- gU return $ (u (mappend x mempty)) === (u x) tAssociative = do x <- gV y <- gV z <- gV u <- gU return $ (u (mappend x (mappend y z))) === (u (mappend (mappend x y) z)) tFoldr = do ls <- listOf gV u <- gU return $ (u (mconcat ls)) === (u (foldr mappend mempty ls)) -- | Tests whether a particular operation is associative, that is: -- -- > a `f` (b `f` c) == (a `f` b) `f` c associative :: (Eq b, Show b) => Gen (a -> a -> a) -- ^ Generates an associative operation -> Gen (a -> b) -- ^ Generates unwrappers -> Gen a -- ^ Generates values -> Gen Property associative gF gU gV = do f <- gF u <- gU a <- gV b <- gV c <- gV return $ (u (a `f` (b `f` c))) === (u ((a `f` b) `f` c)) -- | Tests whether a particular operation is commutative, that is: -- -- > a `f` b == b `f` a commutative :: (Eq b, Show b) => Gen (a -> a -> a) -- ^ Generates a commutative operation -> Gen (a -> b) -- ^ Generates unwrappers -> Gen a -- ^ Generates values -> Gen Property commutative gF gU gV = do f <- gF u <- gU a <- gV b <- gV return $ (u (a `f` b)) === (u (b `f` a)) -- | Tests whether a particular value is the left identity, that is: -- -- > z `f` a == a leftIdentity :: (Eq b, Show b) => Gen (a -> a -> a) -- ^ Generates the operation to test -> Gen (a -> b) -- ^ Generates unwrappers -> Gen a -- ^ Generates identity values -> Gen a -- ^ Generates right-hand side values -> Gen Property leftIdentity gF gU gZ gR = do f <- gF u <- gU z <- gZ r <- gR return $ u (z `f` r) === u r -- | Tests whether a particular value is the right identity, that is: -- -- > a `f` z == a rightIdentity :: (Eq b, Show b) => Gen (a -> a -> a) -- ^ Generates the operation to test -> Gen (a -> b) -- ^ Generates unwrappers -> Gen a -- ^ Generates identity values -> Gen a -- ^ Generates left-hand side values -> Gen Property rightIdentity gF gU gZ gL = do f <- gF u <- gU z <- gZ l <- gL return $ u (l `f` z) === u l