genvalidity-hspec-1.0.0.4: src/Test/Validity/Functor.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | Functor properties
--
-- You will need @TypeApplications@ to use these.
module Test.Validity.Functor
( functorSpec,
functorSpecOnArbitrary,
functorSpecOnGens,
)
where
import Data.Data
import Data.GenValidity
import Data.Kind
import Test.Hspec
import Test.QuickCheck
import Test.Validity.Functions
import Test.Validity.Utils
{-# ANN module "HLint: ignore Functor law" #-}
fmapTypeStr ::
forall (f :: Type -> Type).
(Typeable f) =>
String
fmapTypeStr =
unwords
[ "fmap",
"::",
"(a",
"->",
"b)",
"->",
nameOf @f,
"a",
"->",
nameOf @f,
"b"
]
flTypeStr ::
forall (f :: Type -> Type).
(Typeable f) =>
String
flTypeStr =
unwords ["(<$)", "::", "a", "->", nameOf @f, "b", "->", nameOf @f, "a"]
-- | Standard test spec for properties of Functor instances for values generated with GenValid instances
--
-- Example usage:
--
-- > functorSpecOnArbitrary @[]
functorSpec ::
forall (f :: Type -> Type).
(Eq (f Int), Show (f Int), Functor f, Typeable f, GenValid (f Int)) =>
Spec
functorSpec = functorSpecWithInts @f genValid
-- | Standard test spec for properties of Functor instances for values generated with Arbitrary instances
--
-- Example usage:
--
-- > functorSpecOnArbitrary @[]
functorSpecOnArbitrary ::
forall (f :: Type -> Type).
(Eq (f Int), Show (f Int), Functor f, Typeable f, Arbitrary (f Int)) =>
Spec
functorSpecOnArbitrary = functorSpecWithInts @f arbitrary
functorSpecWithInts ::
forall (f :: Type -> Type).
(Eq (f Int), Show (f Int), Functor f, Typeable f) =>
Gen (f Int) ->
Spec
functorSpecWithInts gen =
functorSpecOnGens
@f
@Int
genValid
"int"
gen
(unwords [nameOf @f, "of ints"])
((+) <$> genValid)
"increments"
((*) <$> genValid)
"scalings"
-- | Standard test spec for properties of Functor instances for values generated by given generators (and names for those generator).
--
-- Example usage:
--
-- > functorSpecOnGens
-- > @[]
-- > @Int
-- > (pure 4) "four"
-- > (genListOf $ pure 5) "list of fives"
-- > ((+) <$> genValid) "additions"
-- > ((*) <$> genValid) "multiplications"
functorSpecOnGens ::
forall (f :: Type -> Type) (a :: Type) (b :: Type) (c :: Type).
( Show a,
Show (f a),
Show (f c),
Eq (f a),
Eq (f c),
Functor f,
Typeable f,
Typeable a,
Typeable b,
Typeable c
) =>
Gen a ->
String ->
Gen (f a) ->
String ->
Gen (b -> c) ->
String ->
Gen (a -> b) ->
String ->
Spec
functorSpecOnGens gena genaname gen genname genf genfname geng gengname =
parallel $
describe ("Functor " ++ nameOf @f) $ do
describe (fmapTypeStr @f) $ do
it
( unwords
[ "satisfies the first Fuctor law: 'fmap id == id' for",
genDescr @(f a) genname
]
)
$ equivalentOnGen (fmap @f id) (id @(f a)) gen shrinkNothing
it
( unwords
[ "satisfieds the second Functor law: 'fmap (f . g) == fmap f . fmap g' for",
genDescr @(f a) genname,
"'s",
"given to",
genDescr @(b -> c) genfname,
"and",
genDescr @(a -> b) gengname
]
)
$ forAll (Anon <$> genf)
$ \(Anon f) ->
forAll (Anon <$> geng) $ \(Anon g) ->
equivalentOnGen
(fmap (f . g))
(fmap f . fmap g)
gen
shrinkNothing
describe (flTypeStr @f)
$ it
( unwords
[ "is equivalent to its default implementation for",
genDescr @a genaname,
"and",
genDescr @(f a) genname
]
)
$ forAll gena
$ \a ->
equivalentOnGen (a <$) (fmap $ const a) gen shrinkNothing