{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} -- | This module is intended to be imported @qualified@, for example: -- -- > import qualified Test.Tasty.Laws.Functor as Functor -- module Test.Tasty.Laws.Functor ( test , testMono , testMonoExhaustive , testPoly , testPolyExhaustive , module Test.SmallCheck.Laws.Functor ) where import Data.Proxy import Data.Functor.Identity (Identity) import Test.Tasty (TestTree, testGroup) import Test.Tasty.SmallCheck (testProperty, Testable) import Test.SmallCheck.Laws.Functor (identity, composition, compositionSum) import Test.SmallCheck.Series (Serial(series), Series) -- | @tasty@ 'TestTree' for 'Functor' laws. The type signature forces the -- parameter to be '()' which, unless you are dealing non-total functions, -- should be enough to test any 'Functor's. test :: (Functor f, Eq (f ()), Show (f ())) => Series IO (f ()) -> TestTree test = testMonoExhaustive -- | @tasty@ 'TestTree' for 'Functor' laws. Monomorphic sum 'Series' for @f@ -- and @g@ in the compose law. -- -- @ -- fmap (\a -> a) . (\a -> a) == fmap (\a -> a) . fmap (\a -> a) -- fmap (\b -> b) . (\b -> b) == fmap (\b -> b) . fmap (\b -> b) -- ... -- @ testMono :: forall f a . ( Eq (f a), Functor f, Show a, Show (f a) , Serial Identity a , Serial IO (a -> a) ) => Series IO (f a) -> TestTree testMono = testWithComp $ \fs -> compositionSum fs (series :: Series IO (a -> a)) (series :: Series IO (a -> a)) -- | @tasty@ 'TestTree' for 'Functor' laws. Monomorphic product 'Series' for -- @f@ and @g@ in the compose law. -- -- @ -- fmap (\a -> a) . (\a -> a) == fmap (\a -> a) . fmap (\a -> a) -- fmap (\a -> a) . (\a -> b) == fmap (\a -> a) . fmap (\a -> b) -- fmap (\a -> a) . (\b -> b) == fmap (\a -> a) . fmap (\b -> b) -- ... -- @ testMonoExhaustive :: forall f a . ( Eq (f a), Functor f, Show a, Show (f a) , Serial Identity a , Serial IO (a -> a) ) => Series IO (f a) -> TestTree testMonoExhaustive = testWithComp $ \fs -> composition fs (series :: Series IO (a -> a)) (series :: Series IO (a -> a)) -- | @tasty@ 'TestTree' for 'Functor' laws. Polymorphic sum 'Series' for -- @f@ and @g@ in the compose law. -- -- @ -- fmap (\a0 -> b0) . (\b0 -> c0) == fmap (\a0 -> b0) . fmap (\b0 -> c0) -- fmap (\a1 -> b1) . (\b1 -> c1) == fmap (\a1 -> a1) . fmap (\b1 -> c1) -- fmap (\a2 -> b2) . (\b2 -> c2) == fmap (\a2 -> a2) . fmap (\b2 -> c2) -- ... -- @ testPoly :: forall f a b c . ( Functor f , Eq (f a), Show a, Show (f a) , Serial Identity a , Eq (f b), Show b, Show (f b) , Serial Identity b , Eq (f c), Show c, Show (f c) , Serial Identity c , Serial IO (a -> b), Serial IO (b -> c) ) => Proxy b -> Proxy c -> Series IO (f a) -> TestTree testPoly _ _ = testWithComp $ \fs -> compositionSum fs (series :: Series IO (b -> c)) (series :: Series IO (a -> b)) -- | @tasty@ 'TestTree' for 'Functor' laws. Polymorphic product 'Series' for -- @f@ and @g@ in the compose law. -- -- @ -- fmap (\a0 -> b0) . (\b0 -> c0) == fmap (\a0 -> b0) . fmap (\b0 -> c0) -- fmap (\a0 -> b0) . (\b0 -> c1) == fmap (\a0 -> a0) . fmap (\b0 -> c1) -- fmap (\a0 -> b0) . (\b0 -> c0) == fmap (\a0 -> a0) . fmap (\b1 -> c1) -- ... -- @ testPolyExhaustive :: forall f a b c . ( Functor f , Eq (f a), Show a, Show (f a) , Serial Identity a , Eq (f b), Show b, Show (f b) , Serial Identity b , Eq (f c), Show c, Show (f c) , Serial Identity c , Serial IO (a -> b), Serial IO (b -> c) ) => Proxy b -> Proxy c -> Series IO (f a) -> TestTree testPolyExhaustive _ _ = testWithComp $ \fs -> composition fs (series :: Series IO (b -> c)) (series :: Series IO (a -> b)) -- * Internal testWithComp :: (Eq (f a), Functor f, Show (f a), Testable IO r) => (Series IO (f a) -> r) -> Series IO (f a) -> TestTree testWithComp comp fs = testGroup "Functor laws" [ testProperty "fmap id ≡ id" $ identity fs , testProperty "fmap (f . g) ≡ fmap f . fmap g" $ comp fs ]