-------------------------------------------------------------------- -- | -- Module : Test.SmallCheck.Series -- Copyright : (c) Colin Runciman et al. -- License : BSD3 -- Maintainer: Roman Cheplyaka -- -- Generation of test data. -------------------------------------------------------------------- {-# LANGUAGE CPP #-} #ifdef GENERICS {-# LANGUAGE DefaultSignatures , FlexibleContexts , TypeOperators , TypeSynonymInstances , FlexibleInstances #-} #endif module Test.SmallCheck.Series ( -- * Basic definitions Depth, Series, Serial(..), -- * Data Generators -- | SmallCheck itself defines data generators for all the data types used -- by the Prelude. -- -- Writing SmallCheck generators for application-specific types is -- straightforward. You need to define a 'series' generator, typically using -- @consN@ family of generic combinators where N is constructor arity. -- -- For example: -- -- >data Tree a = Null | Fork (Tree a) a (Tree a) -- > -- >instance Serial a => Serial (Tree a) where -- > series = cons0 Null \/ cons3 Fork -- -- The default interpretation of depth for datatypes is the depth of nested -- construction: constructor functions, including those for newtypes, build -- results with depth one greater than their deepest argument. But this -- default can be over-ridden by composing a @consN@ application with an -- application of 'depth', like this: -- -- >newtype Light a = Light a -- > -- >instance Serial a => Serial (Light a) where -- > series = cons1 Light . depth 0 -- -- The depth of @Light x@ is just the depth of @x@. cons0, cons1, cons2, cons3, cons4, -- * Function Generators -- | To generate functions of an application-specific argument type -- requires a second method 'coseries'. Again there is a standard -- pattern, this time using the altsN combinators where again N is -- constructor arity. Here are Tree and Light instances: -- -- >coseries rs d = [ \t -> case t of -- > Null -> z -- > Fork t1 x t2 -> f t1 x t2 -- > | z <- alts0 rs d , -- > f <- alts3 rs d ] -- > -- >coseries rs d = [ \l -> case l of -- > Light x -> f x -- > | f <- (alts1 rs . depth 0) d ] alts0, alts1, alts2, alts3, alts4, -- * Automated Derivation of Generators -- | For small examples, Series instances are easy enough to define by hand, -- following the above patterns. But for programs with many or large data -- type definitions, automatic derivation using a tool such as \"derive\" -- is a better option. For example, the following command-line appends to -- Prog.hs the Series instances for all data types defined there. -- -- >$ derive Prog.hs -d Serial --append -- ** Using GHC Generics -- | For GHC users starting from GHC 7.2.1 there's also an option to use GHC's -- Generics to get 'Serial' instance for free. -- -- Example: -- -- >{-# LANGUAGE DeriveGeneric #-} -- >import Test.SmallCheck -- >import GHC.Generics -- > -- >data Tree a = Null | Fork (Tree a) a (Tree a) -- > deriving Generic -- >instance Serial a => Serial (Tree a) -- -- Here we enable the @DeriveGeneric@ extension which allows to derive 'Generic' -- instance for our data type. Then we declare that @Tree a@ is an instance of -- 'Serial', but do not provide any definitions. This causes GHC to use the -- default definitions that use the 'Generic' instance. -- * Other useful definitions (\/), (><), N(..), Nat, Natural, depth ) where import Data.List (intersperse) #ifdef GENERICS import GHC.Generics import Data.DList (DList, toList, fromList) import Data.Monoid (mempty, mappend) #endif -- | Maximum depth of generated test values -- -- For data values, it is the depth of nested constructor applications. -- -- For functional values, it is both the depth of nested case analysis -- and the depth of results. type Depth = Int -- | 'Series' is a function from the depth to a finite list of values. type Series a = Depth -> [a] -- | Sum (union) of series infixr 7 \/ (\/) :: Series a -> Series a -> Series a s1 \/ s2 = \d -> s1 d ++ s2 d -- | Product of series infixr 8 >< (><) :: Series a -> Series b -> Series (a,b) s1 >< s2 = \d -> [(x,y) | x <- s1 d, y <- s2 d] class Serial a where series :: Series a coseries :: Series b -> Series (a->b) #ifdef GENERICS default series :: (Generic a, GSerial (Rep a)) => Series a series = map to . gSeries default coseries :: (Generic a, GSerial (Rep a)) => Series b -> Series (a->b) coseries rs = map (. from) . gCoseries rs class GSerial f where gSeries :: Series (f a) gCoseries :: Series b -> Series (f a -> b) instance GSerial f => GSerial (M1 i c f) where gSeries = map M1 . gSeries gCoseries rs = map (. unM1) . gCoseries rs {-# INLINE gSeries #-} {-# INLINE gCoseries #-} instance Serial c => GSerial (K1 i c) where gSeries = map K1 . series gCoseries rs = map (. unK1) . coseries rs {-# INLINE gSeries #-} {-# INLINE gCoseries #-} instance GSerial U1 where gSeries = cons0 U1 gCoseries rs d = [\U1 -> b | b <- rs d] {-# INLINE gSeries #-} {-# INLINE gCoseries #-} instance (GSerial a, GSerial b) => GSerial (a :*: b) where gSeries d = [x :*: y | x <- gSeries d, y <- gSeries d] gCoseries rs = map uncur . gCoseries (gCoseries rs) where uncur f (x :*: y) = f x y {-# INLINE gSeries #-} {-# INLINE gCoseries #-} instance (GSerialSum a, GSerialSum b) => GSerial (a :+: b) where gSeries = toList . gSeriesSum gCoseries = gCoseriesSum {-# INLINE gSeries #-} {-# INLINE gCoseries #-} class GSerialSum f where gSeriesSum :: DSeries (f a) gCoseriesSum :: Series b -> Series (f a -> b) type DSeries a = Depth -> DList a instance (GSerialSum a, GSerialSum b) => GSerialSum (a :+: b) where gSeriesSum d = fmap L1 (gSeriesSum d) `mappend` fmap R1 (gSeriesSum d) gCoseriesSum rs d = [ \e -> case e of L1 x -> f x R1 y -> g y | f <- gCoseriesSum rs d , g <- gCoseriesSum rs d ] {-# INLINE gSeriesSum #-} {-# INLINE gCoseriesSum #-} instance GSerial f => GSerialSum (C1 c f) where gSeriesSum d | d > 0 = fromList $ gSeries (d-1) | otherwise = mempty gCoseriesSum rs d | d > 0 = gCoseries rs (d-1) | otherwise = [\_ -> x | x <- rs d] {-# INLINE gSeriesSum #-} {-# INLINE gCoseriesSum #-} #endif instance Serial () where series _ = [()] coseries rs d = [ \() -> b | b <- rs d ] instance Serial Int where series d = [(-d)..d] coseries rs d = [ \i -> if i > 0 then f (N (i - 1)) else if i < 0 then g (N (abs i - 1)) else z | z <- alts0 rs d, f <- alts1 rs d, g <- alts1 rs d ] instance Serial Integer where series d = [ toInteger (i :: Int) | i <- series d ] coseries rs d = [ f . (fromInteger :: Integer->Int) | f <- coseries rs d ] -- | 'N' is a wrapper for 'Integral' types that causes only non-negative values -- to be generated. Generated functions of type @N a -> b@ do not distinguish -- different negative values of @a@. -- -- See also 'Nat' and 'Natural'. newtype N a = N a deriving (Eq, Ord) instance Show a => Show (N a) where show (N i) = show i instance (Integral a, Serial a) => Serial (N a) where series d = map N [0..d'] where d' = fromInteger (toInteger d) coseries rs d = [ \(N i) -> if i > 0 then f (N (i - 1)) else z | z <- alts0 rs d, f <- alts1 rs d ] type Nat = N Int type Natural = N Integer instance Serial Float where series d = [ encodeFloat sig exp | (sig,exp) <- series d, odd sig || sig==0 && exp==0 ] coseries rs d = [ f . decodeFloat | f <- coseries rs d ] instance Serial Double where series d = [ frac (x :: Float) | x <- series d ] coseries rs d = [ f . (frac :: Double->Float) | f <- coseries rs d ] frac :: (Real a, Fractional a, Real b, Fractional b) => a -> b frac = fromRational . toRational instance Serial Char where series d = take (d+1) ['a'..'z'] coseries rs d = [ \c -> f (N (fromEnum c - fromEnum 'a')) | f <- coseries rs d ] instance (Serial a, Serial b) => Serial (a,b) where series = series >< series coseries rs = map uncurry . (coseries $ coseries rs) instance (Serial a, Serial b, Serial c) => Serial (a,b,c) where series = \d -> [(a,b,c) | (a,(b,c)) <- series d] coseries rs = map uncurry3 . (coseries $ coseries $ coseries rs) instance (Serial a, Serial b, Serial c, Serial d) => Serial (a,b,c,d) where series = \d -> [(a,b,c,d) | (a,(b,(c,d))) <- series d] coseries rs = map uncurry4 . (coseries $ coseries $ coseries $ coseries rs) uncurry3 :: (a->b->c->d) -> ((a,b,c)->d) uncurry3 f (x,y,z) = f x y z uncurry4 :: (a->b->c->d->e) -> ((a,b,c,d)->e) uncurry4 f (w,x,y,z) = f w x y z cons0 :: a -> Series a cons0 c _ = [c] cons1 :: Serial a => (a->b) -> Series b cons1 c d = [c z | d > 0, z <- series (d-1)] cons2 :: (Serial a, Serial b) => (a->b->c) -> Series c cons2 c d = [c y z | d > 0, (y,z) <- series (d-1)] cons3 :: (Serial a, Serial b, Serial c) => (a->b->c->d) -> Series d cons3 c d = [c x y z | d > 0, (x,y,z) <- series (d-1)] cons4 :: (Serial a, Serial b, Serial c, Serial d) => (a->b->c->d->e) -> Series e cons4 c d = [c w x y z | d > 0, (w,x,y,z) <- series (d-1)] alts0 :: Series a -> Series a alts0 as d = as d alts1 :: Serial a => Series b -> Series (a->b) alts1 bs d = if d > 0 then coseries bs (dec d) else [\_ -> x | x <- bs d] alts2 :: (Serial a, Serial b) => Series c -> Series (a->b->c) alts2 cs d = if d > 0 then coseries (coseries cs) (dec d) else [\_ _ -> x | x <- cs d] alts3 :: (Serial a, Serial b, Serial c) => Series d -> Series (a->b->c->d) alts3 ds d = if d > 0 then coseries (coseries (coseries ds)) (dec d) else [\_ _ _ -> x | x <- ds d] alts4 :: (Serial a, Serial b, Serial c, Serial d) => Series e -> Series (a->b->c->d->e) alts4 es d = if d > 0 then coseries (coseries (coseries (coseries es))) (dec d) else [\_ _ _ _ -> x | x <- es d] instance Serial Bool where series = cons0 True \/ cons0 False coseries rs d = [ \x -> if x then r1 else r2 | r1 <- rs d, r2 <- rs d ] instance Serial a => Serial (Maybe a) where series = cons0 Nothing \/ cons1 Just coseries rs d = [ \m -> case m of Nothing -> z Just x -> f x | z <- alts0 rs d , f <- alts1 rs d ] instance (Serial a, Serial b) => Serial (Either a b) where series = cons1 Left \/ cons1 Right coseries rs d = [ \e -> case e of Left x -> f x Right y -> g y | f <- alts1 rs d , g <- alts1 rs d ] instance Serial a => Serial [a] where series = cons0 [] \/ cons2 (:) coseries rs d = [ \xs -> case xs of [] -> y (x:xs') -> f x xs' | y <- alts0 rs d , f <- alts2 rs d ] -- Thanks to Ralf Hinze for the definition of coseries -- using the nest auxiliary. instance (Serial a, Serial b) => Serial (a->b) where series = coseries series coseries rs d = [ \ f -> g [ f a | a <- args ] | g <- nest args d ] where args = series d nest [] _ = [ \[] -> c | c <- rs d ] nest (a:as) _ = [ \(b:bs) -> f b bs | f <- coseries (nest as) d ] -- | For customising the depth measure. Use with care! depth :: Depth -> Depth -> Depth depth d d' | d >= 0 = d'+1-d | otherwise = error "SmallCheck.depth: argument < 0" dec :: Depth -> Depth dec d | d > 0 = d-1 | otherwise = error "SmallCheck.dec: argument <= 0" inc :: Depth -> Depth inc d = d+1 -- show the extension of a function (in part, bounded both by -- the number and depth of arguments) instance (Serial a, Show a, Show b) => Show (a->b) where show f = if maxarheight == 1 && sumarwidth + length ars * length "->;" < widthLimit then "{"++( concat $ intersperse ";" $ [a++"->"++r | (a,r) <- ars] )++"}" else concat $ [a++"->\n"++indent r | (a,r) <- ars] where ars = take lengthLimit [ (show x, show (f x)) | x <- series depthLimit ] maxarheight = maximum [ max (height a) (height r) | (a,r) <- ars ] sumarwidth = sum [ length a + length r | (a,r) <- ars] indent = unlines . map (" "++) . lines height = length . lines (widthLimit,lengthLimit,depthLimit) = (80,20,3)::(Int,Int,Depth)