{-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} #if __GLASGOW_HASKELL__ >= 708 {-# LANGUAGE EmptyCase #-} {-# LANGUAGE RoleAnnotations #-} #endif {-# OPTIONS_GHC -fno-warn-name-shadowing #-} {-# OPTIONS_GHC -fno-warn-unused-matches #-} #if __GLASGOW_HASKELL__ >= 800 {-# OPTIONS_GHC -Wno-unused-foralls #-} #endif {-| Module: FunctorSpec Copyright: (C) 2015-2017 Ryan Scott License: BSD-style (see the file LICENSE) Maintainer: Ryan Scott Portability: Template Haskell @hspec@ tests for derived 'Functor', 'Foldable', and 'Traversable' instances. -} module FunctorSpec where import Data.Char (chr) import Data.Foldable (fold) import Data.Deriving import Data.Functor.Classes (Eq1) import Data.Functor.Compose (Compose(..)) import Data.Functor.Identity (Identity(..)) import Data.Monoid import Data.Orphans () import GHC.Exts (Int#) import Prelude () import Prelude.Compat import Test.Hspec import Test.Hspec.QuickCheck (prop) import Test.QuickCheck (Arbitrary) ------------------------------------------------------------------------------- -- Adapted from the test cases from -- https://ghc.haskell.org/trac/ghc/attachment/ticket/2953/deriving-functor-tests.patch -- Plain data types data Strange a b c = T1 a b c | T2 [a] [b] [c] -- lists | T3 [[a]] [[b]] [[c]] -- nested lists | T4 (c,(b,b),(c,c)) -- tuples | T5 ([c],Strange a b c) -- tycons type IntFun a b = (b -> Int) -> a data StrangeFunctions a b c = T6 (a -> c) -- function types | T7 (a -> (c,a)) -- functions and tuples | T8 ((b -> a) -> c) -- continuation | T9 (IntFun b c) -- type synonyms data StrangeGADT a b where T10 :: Ord d => d -> StrangeGADT c d T11 :: Int -> StrangeGADT e Int T12 :: c ~ Int => c -> StrangeGADT f Int T13 :: i ~ Int => Int -> StrangeGADT h i T14 :: k ~ Int => k -> StrangeGADT j k T15 :: (n ~ c, c ~ Int) => Int -> c -> StrangeGADT m n data NotPrimitivelyRecursive a b = S1 (NotPrimitivelyRecursive (a,a) (b, a)) | S2 a | S3 b newtype OneTwoCompose f g a b = OneTwoCompose (Either (f (g a)) (f (g b))) deriving (Arbitrary, Eq, Show) newtype ComplexConstraint f g a b = ComplexConstraint (f Int Int (g a,a,b)) data Universal a b = Universal (forall b. (b,[a])) | Universal2 (forall f. Functor (f a) => f a b) | Universal3 (forall a. Maybe a) -- reuse a | NotReallyUniversal (forall b. a) data Existential a b = forall a. ExistentialList [a] | forall f. Traversable (f a) => ExistentialFunctor (f a b) | forall b. SneakyUseSameName (Maybe b) data IntHash a b = IntHash Int# Int# | IntHashTuple Int# a b (a, b, Int, IntHash Int (a, b, Int)) data IntHashFun a b = IntHashFun ((((a -> Int#) -> b) -> Int#) -> a) data Empty1 a data Empty2 a #if __GLASGOW_HASKELL__ >= 708 type role Empty2 nominal #endif -- Data families data family StrangeFam x y z data instance StrangeFam a b c = T1Fam a b c | T2Fam [a] [b] [c] -- lists | T3Fam [[a]] [[b]] [[c]] -- nested lists | T4Fam (c,(b,b),(c,c)) -- tuples | T5Fam ([c],Strange a b c) -- tycons data family StrangeFunctionsFam x y z data instance StrangeFunctionsFam a b c = T6Fam (a -> c) -- function types | T7Fam (a -> (c,a)) -- functions and tuples | T8Fam ((b -> a) -> c) -- continuation | T9Fam (IntFun b c) -- type synonyms data family StrangeGADTFam x y data instance StrangeGADTFam a b where T10Fam :: Ord d => d -> StrangeGADTFam c d T11Fam :: Int -> StrangeGADTFam e Int T12Fam :: c ~ Int => c -> StrangeGADTFam f Int T13Fam :: i ~ Int => Int -> StrangeGADTFam h i T14Fam :: k ~ Int => k -> StrangeGADTFam j k T15Fam :: (n ~ c, c ~ Int) => Int -> c -> StrangeGADTFam m n data family NotPrimitivelyRecursiveFam x y data instance NotPrimitivelyRecursiveFam a b = S1Fam (NotPrimitivelyRecursive (a,a) (b, a)) | S2Fam a | S3Fam b data family OneTwoComposeFam (j :: * -> *) (k :: * -> *) x y newtype instance OneTwoComposeFam f g a b = OneTwoComposeFam (Either (f (g a)) (f (g b))) deriving (Arbitrary, Eq, Show) data family ComplexConstraintFam (j :: * -> * -> * -> *) (k :: * -> *) x y newtype instance ComplexConstraintFam f g a b = ComplexConstraintFam (f Int Int (g a,a,b)) data family UniversalFam x y data instance UniversalFam a b = UniversalFam (forall b. (b,[a])) | Universal2Fam (forall f. Functor (f a) => f a b) | Universal3Fam (forall a. Maybe a) -- reuse a | NotReallyUniversalFam (forall b. a) data family ExistentialFam x y data instance ExistentialFam a b = forall a. ExistentialListFam [a] | forall f. Traversable (f a) => ExistentialFunctorFam (f a b) | forall b. SneakyUseSameNameFam (Maybe b) data family IntHashFam x y data instance IntHashFam a b = IntHashFam Int# Int# | IntHashTupleFam Int# a b (a, b, Int, IntHashFam Int (a, b, Int)) data family IntHashFunFam x y data instance IntHashFunFam a b = IntHashFunFam ((((a -> Int#) -> b) -> Int#) -> a) ------------------------------------------------------------------------------- -- Plain data types $(deriveFunctor ''Strange) $(deriveFoldable ''Strange) $(deriveTraversable ''Strange) $(deriveFunctor ''StrangeFunctions) $(deriveFoldable ''StrangeGADT) $(deriveFunctor ''NotPrimitivelyRecursive) $(deriveFoldable ''NotPrimitivelyRecursive) $(deriveTraversable ''NotPrimitivelyRecursive) $(deriveFunctor ''OneTwoCompose) $(deriveFoldable ''OneTwoCompose) $(deriveTraversable ''OneTwoCompose) instance Functor (f Int Int) => Functor (ComplexConstraint f g a) where fmap = $(makeFmap ''ComplexConstraint) instance Foldable (f Int Int) => Foldable (ComplexConstraint f g a) where foldr = $(makeFoldr ''ComplexConstraint) foldMap = $(makeFoldMap ''ComplexConstraint) fold = $(makeFold ''ComplexConstraint) foldl = $(makeFoldl ''ComplexConstraint) instance Traversable (f Int Int) => Traversable (ComplexConstraint f g a) where traverse = $(makeTraverse ''ComplexConstraint) sequenceA = $(makeSequenceA ''ComplexConstraint) mapM = $(makeMapM ''ComplexConstraint) sequence = $(makeSequence ''ComplexConstraint) $(deriveFunctor ''Universal) $(deriveFunctor ''Existential) $(deriveFoldable ''Existential) $(deriveTraversable ''Existential) $(deriveFunctor ''IntHash) $(deriveFoldable ''IntHash) $(deriveTraversable ''IntHash) $(deriveFunctor ''IntHashFun) $(deriveFunctor ''Empty1) $(deriveFoldable ''Empty1) $(deriveTraversable ''Empty1) -- Use EmptyCase here $(deriveFunctorOptions defaultFFTOptions{ fftEmptyCaseBehavior = True } ''Empty2) $(deriveFoldableOptions defaultFFTOptions{ fftEmptyCaseBehavior = True } ''Empty2) $(deriveTraversableOptions defaultFFTOptions{ fftEmptyCaseBehavior = True } ''Empty2) #if MIN_VERSION_template_haskell(2,7,0) -- Data families $(deriveFunctor 'T1Fam) $(deriveFoldable 'T2Fam) $(deriveTraversable 'T3Fam) $(deriveFunctor 'T6Fam) $(deriveFoldable 'T10Fam) $(deriveFunctor 'S1Fam) $(deriveFoldable 'S2Fam) $(deriveTraversable 'S3Fam) $(deriveFunctor 'OneTwoComposeFam) $(deriveFoldable 'OneTwoComposeFam) $(deriveTraversable 'OneTwoComposeFam) instance Functor (f Int Int) => Functor (ComplexConstraintFam f g a) where fmap = $(makeFmap 'ComplexConstraintFam) instance Foldable (f Int Int) => Foldable (ComplexConstraintFam f g a) where foldr = $(makeFoldr 'ComplexConstraintFam) foldMap = $(makeFoldMap 'ComplexConstraintFam) fold = $(makeFold 'ComplexConstraintFam) foldl = $(makeFoldl 'ComplexConstraintFam) instance Traversable (f Int Int) => Traversable (ComplexConstraintFam f g a) where traverse = $(makeTraverse 'ComplexConstraintFam) sequenceA = $(makeSequenceA 'ComplexConstraintFam) mapM = $(makeMapM 'ComplexConstraintFam) sequence = $(makeSequence 'ComplexConstraintFam) $(deriveFunctor 'UniversalFam) $(deriveFunctor 'ExistentialListFam) $(deriveFoldable 'ExistentialFunctorFam) $(deriveTraversable 'SneakyUseSameNameFam) $(deriveFunctor 'IntHashFam) $(deriveFoldable 'IntHashTupleFam) $(deriveTraversable 'IntHashFam) $(deriveFunctor 'IntHashFunFam) #endif ------------------------------------------------------------------------------- prop_FunctorLaws :: (Functor f, Eq (f a), Eq (f c)) => (b -> c) -> (a -> b) -> f a -> Bool prop_FunctorLaws f g x = fmap id x == x && fmap (f . g) x == (fmap f . fmap g) x prop_FunctorEx :: (Functor f, Eq (f [Int])) => f [Int] -> Bool prop_FunctorEx = prop_FunctorLaws reverse (++ [42]) prop_FoldableLaws :: (Eq a, Eq b, Eq z, Monoid a, Monoid b, Foldable f) => (a -> b) -> (a -> z -> z) -> z -> f a -> Bool prop_FoldableLaws f h z x = fold x == foldMap id x && foldMap f x == foldr (mappend . f) mempty x && foldr h z x == appEndo (foldMap (Endo . h) x) z prop_FoldableEx :: Foldable f => f [Int] -> Bool prop_FoldableEx = prop_FoldableLaws reverse ((+) . length) 0 prop_TraversableLaws :: forall t f g a b c. (Applicative f, Applicative g, Traversable t, Eq (t (f a)), Eq (g (t a)), Eq (g (t b)), Eq (t a), Eq (t c), Eq1 f, Eq1 g) => (a -> f b) -> (b -> f c) -> (forall x. f x -> g x) -> t a -> Bool prop_TraversableLaws f g t x = (t . traverse f) x == traverse (t . f) x && traverse Identity x == Identity x && traverse (Compose . fmap g . f) x == (Compose . fmap (traverse g) . traverse f) x && (t . sequenceA) y == (sequenceA . fmap t) y && (sequenceA . fmap Identity) y == Identity y && (sequenceA . fmap Compose) z == (Compose . fmap sequenceA . sequenceA) z where y :: t (f a) y = fmap pure x z :: t (f (g a)) z = fmap (fmap pure) y prop_TraversableEx :: (Traversable t, Eq (t [[Int]]), Eq (t [Int]), Eq (t String), Eq (t Char)) => t [Int] -> Bool prop_TraversableEx = prop_TraversableLaws (replicate 2 . map (chr . abs)) (++ "Hello") reverse ------------------------------------------------------------------------------- main :: IO () main = hspec spec spec :: Spec spec = parallel $ do describe "OneTwoCompose Maybe ((,) Bool) [Int] [Int]" $ do prop "satisfies the Functor laws" (prop_FunctorEx :: OneTwoCompose Maybe ((,) Bool) [Int] [Int] -> Bool) prop "satisfies the Foldable laws" (prop_FoldableEx :: OneTwoCompose Maybe ((,) Bool) [Int] [Int] -> Bool) prop "satisfies the Traversable laws" (prop_TraversableEx :: OneTwoCompose Maybe ((,) Bool) [Int] [Int] -> Bool) #if MIN_VERSION_template_haskell(2,7,0) describe "OneTwoComposeFam Maybe ((,) Bool) [Int] [Int]" $ do prop "satisfies the Functor laws" (prop_FunctorEx :: OneTwoComposeFam Maybe ((,) Bool) [Int] [Int] -> Bool) prop "satisfies the Foldable laws" (prop_FoldableEx :: OneTwoComposeFam Maybe ((,) Bool) [Int] [Int] -> Bool) prop "satisfies the Traversable laws" (prop_TraversableEx :: OneTwoComposeFam Maybe ((,) Bool) [Int] [Int] -> Bool) #endif