{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE AllowAmbiguousTypes #-} -- | Applicative properties -- -- You will need @TypeApplications@ to use these. module Test.Validity.Applicative ( applicativeSpecOnValid , applicativeSpec , applicativeSpecOnArbitrary , applicativeSpecOnGens ) where import Data.Data import Data.GenValidity import Test.Hspec import Test.QuickCheck import Test.Validity.Functions import Test.Validity.Utils {-# ANN module "HLint: ignore Avoid lambda" #-} pureTypeStr :: forall (f :: * -> *). (Typeable f) => String pureTypeStr = unwords ["pure", "::", "a", "->", nameOf @f, "a"] seqTypeStr :: forall (f :: * -> *). (Typeable f) => String seqTypeStr = unwords [ "(<*>)" , "::" , nameOf @f , "(a" , "->" , "b)" , "->" , nameOf @f , "a" , "->" , nameOf @f , "b" ] seqrTypeStr :: forall (f :: * -> *). (Typeable f) => String seqrTypeStr = unwords [ "(*>)" , "::" , nameOf @f , "a" , "->" , nameOf @f , "b" , "->" , nameOf @f , "b" ] seqlTypeStr :: forall (f :: * -> *). (Typeable f) => String seqlTypeStr = unwords [ "(<*)" , "::" , nameOf @f , "a" , "->" , nameOf @f , "b" , "->" , nameOf @f , "a" ] -- | Standard test spec for properties of Applicative instances for values generated with GenValid instances -- -- Example usage: -- -- > applicativeSpecOnArbitrary @[] applicativeSpecOnValid :: forall (f :: * -> *). (Eq (f Int), Show (f Int), Applicative f, Typeable f, GenValid (f Int)) => Spec applicativeSpecOnValid = applicativeSpecWithInts @f genValid -- | Standard test spec for properties of Applicative instances for values generated with GenUnchecked instances -- -- Example usage: -- -- > applicativeSpecOnArbitrary @[] applicativeSpec :: forall (f :: * -> *). ( Eq (f Int) , Show (f Int) , Applicative f , Typeable f , GenUnchecked (f Int) ) => Spec applicativeSpec = applicativeSpecWithInts @f genUnchecked -- | Standard test spec for properties of Applicative instances for values generated with Arbitrary instances -- -- Example usage: -- -- > applicativeSpecOnArbitrary @[] applicativeSpecOnArbitrary :: forall (f :: * -> *). (Eq (f Int), Show (f Int), Applicative f, Typeable f, Arbitrary (f Int)) => Spec applicativeSpecOnArbitrary = applicativeSpecWithInts @f arbitrary applicativeSpecWithInts :: forall (f :: * -> *). (Show (f Int), Eq (f Int), Applicative f, Typeable f) => Gen (f Int) -> Spec applicativeSpecWithInts gen = applicativeSpecOnGens @f @Int genUnchecked "int" gen (unwords [nameOf @f, "of ints"]) gen (unwords [nameOf @f, "of ints"]) ((+) <$> genUnchecked) "increments" (pure <$> ((+) <$> genUnchecked)) (unwords [nameOf @f, "of increments"]) (pure <$> ((*) <$> genUnchecked)) (unwords [nameOf @f, "of scalings"]) -- | Standard test spec for properties of Applicative instances for values generated by given generators (and names for those generator). -- -- Unless you are building a specific regression test, you probably want to use the other 'applicativeSpec' functions. -- -- Example usage: -- -- > applicativeSpecOnGens -- > @Maybe -- > @String -- > (pure "ABC") -- > "ABC" -- > (Just <$> pure "ABC") -- > "Just an ABC" -- > (pure Nothing) -- > "purely Nothing" -- > ((++) <$> genValid) -- > "prepends" -- > (pure <$> ((++) <$> genValid)) -- > "prepends in a Just" -- > (pure <$> (flip (++) <$> genValid)) -- > "appends in a Just" applicativeSpecOnGens :: forall (f :: * -> *) (a :: *) (b :: *) (c :: *). ( Show a , Eq a , Show (f a) , Eq (f a) , Show (f b) , Eq (f b) , Show (f c) , Eq (f c) , Applicative f , Typeable f , Typeable a , Typeable b , Typeable c ) => Gen a -> String -> Gen (f a) -> String -> Gen (f b) -> String -> Gen (a -> b) -> String -> Gen (f (a -> b)) -> String -> Gen (f (b -> c)) -> String -> Spec applicativeSpecOnGens gena genaname gen genname genb genbname genfa genfaname genffa genffaname genffb genffbname = parallel $ describe ("Applicative " ++ nameOf @f) $ do describe (unwords [pureTypeStr @f, "and", seqTypeStr @f]) $ do it (unwords [ "satisfy the identity law: 'pure id <*> v = v' for" , genDescr @(f a) genname ]) $ equivalentOnGen (pure id <*>) id gen shrinkNothing it (unwords [ "satisfy the composition law: 'pure (.) <*> u <*> v <*> w = u <*> (v <*> w)' for" , genDescr @(f (b -> c)) genffbname , "composed with" , genDescr @(f (a -> b)) genffaname , "and applied to" , genDescr @(f a) genname ]) $ equivalentOnGens3 (\(Anon u) (Anon v) w -> pure (.) <*> (u :: f (b -> c)) <*> (v :: f (a -> b)) <*> (w :: f a) :: f c) (\(Anon u) (Anon v) w -> u <*> (v <*> w) :: f c) ((,,) <$> (Anon <$> genffb) <*> (Anon <$> genffa) <*> gen) shrinkNothing it (unwords [ "satisfy the homomorphism law: 'pure f <*> pure x = pure (f x)' for" , genDescr @(a -> b) genfaname , "sequenced with" , genDescr @a genaname ]) $ equivalentOnGens2 (\(Anon f) x -> pure f <*> pure x :: f b) (\(Anon f) x -> pure $ f x :: f b) ((,) <$> (Anon <$> genfa) <*> gena) shrinkNothing it (unwords [ "satisfy the interchange law: 'u <*> pure y = pure ($ y) <*> u' for" , genDescr @(f (a -> b)) genffaname , "sequenced with" , genDescr @a genaname ]) $ equivalentOnGens2 (\(Anon u) y -> u <*> pure y :: f b) (\(Anon u) y -> pure ($ y) <*> u :: f b) ((,) <$> (Anon <$> genffa) <*> gena) shrinkNothing it (unwords [ "satisfy the law about the functor instance: fmap f x = pure f <*> x for" , genDescr @(a -> b) genfaname , "mapped over" , genDescr @(f a) genname ]) $ equivalentOnGens2 (\(Anon f) x -> fmap f x) (\(Anon f) x -> pure f <*> x) ((,) <$> (Anon <$> genfa) <*> gen) shrinkNothing describe (seqrTypeStr @f) $ it (unwords [ "is equivalent to its default implementation 'u *> v = pure (const id) <*> u <*> v' for" , genDescr @(f a) genname , "in front of" , genDescr @b genbname ]) $ equivalentOnGens2 (\u v -> u *> v) (\u v -> pure (const id) <*> u <*> v) ((,) <$> gen <*> genb) shrinkNothing describe (seqlTypeStr @f) $ it (unwords [ "is equivalent to its default implementation 'u <* v = pure const <*> u <*> v' for" , genDescr @b genbname , "behind" , genDescr @(f a) genname ]) $ equivalentOnGens2 (\u v -> u <* v) (\u v -> pure const <*> u <*> v) ((,) <$> gen <*> genb) shrinkNothing