\section{Generator Library}
This module is a library of generators for standard base types.
Most of the generators here are created from strategies over enumerations,
called enumerative generators, but some are manually coded for efficiency.
\begin{code}
module Test.GenCheck.Generator.BaseGens where
import System.Random (StdGen,randomR, Random(),RandomGen(), randoms, randomRs)
import Math.Combinat (combine)
import Numeric.IEEE
import Test.GenCheck.Base.Base (Rank)
import Test.GenCheck.Generator.BaseEnum
import Test.GenCheck.Generator.EnumStrat
import Test.GenCheck.Generator.Generator (Generator, Testable(..), StandardGens(..))
\end{code}
Generic generator for Haskell enumerated base types.
Base type values are all rank 1; baseGen defines such a range.
Note that the exhaustive and random generators are implemented
without using the base type enumerations, while the extreme and uniform generators
apply the enumerative strategy to the underlying enumeration.
Bypassing the enumeration avoids the overhead of the selection function,
improving the performance of the generator.
\begin{code}
baseGen :: [a] -> Generator a
baseGen xs r = if r == 1 then xs else []
baseEnumGen :: EnumStrat -> BaseEnum a -> Generator a
baseEnumGen strat e r | r ==1 = map (getBaseUnsafe e) (strat (baseCount e))
baseEnumGen _ _ _ | otherwise = []
baseEnumGCStdGens :: (EnumGC a) => StandardGens a
baseEnumGCStdGens = baseEnumGCGens base
baseEnumGCGens :: BaseEnum a -> StandardGens a
baseEnumGCGens e = StdGens allGen xtrmGen uniGen randGen
where
allGen = baseEnumGen exhaustG e
xtrmGen = baseEnumGen extreme e
uniGen = \m' -> baseEnumGen (uniform m') e
randGen = \s' -> baseEnumGen (randG s') e
genBaseRangeAll, genBaseRangeExt :: Enum a => (a,a) -> Generator a
genBaseRangeAll (l,u) = baseGen [l..u]
genBaseRangeExt (l,u) = baseEnumGen extreme (enumBaseRange (l,u))
genBaseRangeUni :: Enum a => (a,a) -> Int -> Generator a
genBaseRangeUni (l,u) k = baseEnumGen (uniform k) (enumBaseRange (l,u))
genBaseRangeRnd :: (RandomGen t, Random a) => (a,a) -> t -> Generator a
genBaseRangeRnd (l',u') t = baseGen $ rg (l',u') t
where
rg (l,u) s = let (x,s') = (randomR (l,u) s) in x : (rg (l,u) s')
genBaseStdGens :: (Enum a, Random a) => (a,a) -> StandardGens a
genBaseStdGens rng = StdGens (genBaseRangeAll rng) (genBaseRangeExt rng)
(genBaseRangeUni rng) (genBaseRangeRnd rng)
instance Testable Int where
stdTestGens = genBaseStdGens (100, 100)
\end{code}
For bounded enumerative types, the minBound and maxBounds can be used.
The type needs to be provided explicitly, otherwise it would be ambiguous.
Copy and paste the functions as required for other enumerative bounded types,
or just use the ranged generators with (minBound, maxBound) as the argument.
These are the bounded Int generators.
\begin{code}
genIntAll, genIntExt :: Generator Int
genIntAll = baseGen [(minBound::Int)..(maxBound::Int)]
genIntExt = baseEnumGen extreme $ enumBaseRange ((minBound::Int), (maxBound::Int))
genIntUni :: Int -> Generator Int
genIntUni k = baseEnumGen (uniform k) $ enumBaseRange ((minBound::Int), (maxBound::Int))
genIntRnd :: RandomGen t => t -> Generator Int
genIntRnd t = baseGen $ rg ((minBound::Int), (maxBound::Int)) t
where rg (l,u) s = let (x,s') = (randomR (l,u) s) in x : (rg (l,u) s')
\end{code}
These are some Char and String generators. Note that the exhaustive generator
does not use the enumeration, but the other generators use the base type
enumeration and an enumerative strategy.
The Testable instance default generator for Char is the default character
range.
\begin{code}
instance Testable Char where
stdTestGens = StdGens genDfltCharAll genDfltCharExt genDfltCharUni genDfltCharRnd
genLowCharAll, genDfltCharAll, genUpperCharAll, genDigitCharAll :: Generator Char
genDfltCharAll = baseGen [' '..'~']
genLowCharAll = baseGen ['a'..'z']
genUpperCharAll = baseGen ['A'..'Z']
genDigitCharAll = baseGen ['0'..'9']
genLowCharRnd, genDfltCharRnd, genUpperCharRnd, genDigitCharRnd :: StdGen -> Generator Char
genDfltCharRnd s = baseEnumGen (randG s) enumDfltChar
genLowCharRnd s = baseEnumGen (randG s) enumLowChar
genUpperCharRnd s = baseEnumGen (randG s) enumUpperChar
genDigitCharRnd s = baseEnumGen (randG s) enumDigitChar
genLowCharExt, genDfltCharExt, genUpperCharExt, genDigitCharExt :: Generator Char
genDfltCharExt = baseEnumGen extreme enumDfltChar
genLowCharExt = baseEnumGen extreme enumLowChar
genUpperCharExt = baseEnumGen extreme enumUpperChar
genDigitCharExt = baseEnumGen extreme enumDigitChar
genLowCharUni, genDfltCharUni, genUpperCharUni, genDigitCharUni :: Int -> Generator Char
genDfltCharUni k = baseEnumGen (uniform k) enumDfltChar
genLowCharUni k = baseEnumGen (uniform k) enumLowChar
genUpperCharUni k = baseEnumGen (uniform k) enumUpperChar
genDigitCharUni k = baseEnumGen (uniform k) enumDigitChar
\end{code}
String (lists of character) generators where string length is the rank.
These could have been generated as list structures composed with characters,
but strings are used frequently so a more efficient implementation is desirable.
\begin{code}
genStrRangeAll :: (Char,Char) -> Generator String
genStrRangeAll (a,z) r = combine r [a..z]
genStrDfltCharAll, genStrLowCharAll, genStrUpperCharAll, genStrDigitCharAll :: Generator String
genStrDfltCharAll = genStrRangeAll (' ','~')
genStrLowCharAll = genStrRangeAll ('a','z')
genStrUpperCharAll = genStrRangeAll ('A','Z')
genStrDigitCharAll = genStrRangeAll ('0','9')
genStrRangeRnd :: StdGen -> (Char,Char) -> Generator String
genStrRangeRnd s (a,z) r =
let (str,s') = bldStr s (a,z) r in str : (genStrRangeRnd s' (a,z) r)
genStrLowRnd, genStrUpperRnd, genStrDigitRnd :: StdGen -> Generator String
genStrLowRnd s = genStrRangeRnd s ('a','z')
genStrUpperRnd s = genStrRangeRnd s ('A','Z')
genStrDigitRnd s = genStrRangeRnd s ('0','9')
bldStr :: StdGen -> (Char,Char) -> Rank -> (String, StdGen)
bldStr s (_,_) 0 = ("",s)
bldStr s (a,z) r =
let (c,s') = randomR (a,z) s
(str,s'') = bldStr s' (a,z) (r1)
in (c : str, s'')
\end{code}
Generating other scalars is a bit trickier.
Ratios: we can build enumerated generators of ratios,
or use the diagonalized counting of fractions to get a non-enumerated generator.
--\begin{code}
genRatioAll,genRatioXtrm :: Generator (Ratio Int)
genRatioAll = baseEnumGen exhaustG enumBasePosRatio
genRatioXtrm = baseEnumGen extreme enumBasePosRatio
genRatioRnd :: StdGen -> Generator (Ratio Int)
genRatioRnd s = baseEnumGen (randG s) enumBasePosRatio
genRatioUni :: Int -> Generator (Ratio Int)
genRatioUni n = baseEnumGen (uniform n) enumBasePosRatio
-- non-enumerated generator of positive ratios, Cantor counting (i.e. 1/1, 1/2, 2/1, 1/3, 2/2, 3/1, etc.)
diagInt :: Integral a => a -> [Ratio a]
diagInt n | n == 1 = [(1 % 1)]
diagInt n | n > 1 = [(i % (n-i+1)) | i<-[1..n]]
diagInt _ | otherwise = undefined
genPosRatio' :: (Num a, Eq a, Integral a1) => a -> [Ratio a1]
genPosRatio' r | r == 1 = foldr (++) [] $ map diagInt [1..]
genPosRatio' _ | otherwise = []
genRatio' :: (Num a, Eq a, Integral a1) => a -> [Ratio a1]
genRatio' r | r == 1 = let g = genPosRatio' (1::Integer) in
interleave g $ map negate g
genRatio' _ | otherwise = undefined
--\end{code}
Doubles and Floats: hard code some sample generators, which include extreme
value generator, and a random generator. Extreme values use the constants from
the IEEE754 Hackage. There are no exhaustive generators for these values.
It would have been nice to make general RealFloat versions of all of these
generators, but Haskell couldn't handle the ambiguous typing.
\begin{code}
genDblRnd :: StdGen -> Generator Double
genDblRnd s r | r == 1 = (randoms s:: [Double])
genDblRnd _ _ | otherwise = ([] :: [Double])
genDblRangeRnd :: (Double,Double) -> StdGen -> Generator Double
genDblRangeRnd (l,u) s r | r == 1 = (randomRs (l,u) s :: [Double])
genDblRangeRnd _ _ _ | otherwise = ([] :: [Double])
genDblXtrm :: Generator Double
genDblXtrm r | r == 1 =
[minNormal::Double, maxFinite::Double, negate (minNormal::Double), negate (maxFinite::Double), (0::Double),
epsilon::Double, negate (epsilon::Double), infinity::Double, negate (infinity::Double),
nan::Double, (nanWithPayload (maxNaNPayload (1::Double)))::Double, (nanWithPayload 1)::Double]
genDblXtrm _ | otherwise = []
genDblUni :: Int -> Generator Double
genDblUni m r | (r==1) && (m > 2) =
let l = (negate maxFinite::Double)
u = maxFinite::Double
delta = (u / ((fromInteger.toInteger) (m1))) (l / ((fromInteger.toInteger) (m1)))
in take m $ iterate ((+) delta) l
genDblUni m r | r==1 && (m <= 2) = [ (negate maxFinite::Double), maxFinite::Double ]
genDblUni _ _ | otherwise = []
genDblRangeUni :: (Double,Double) -> Int -> Generator Double
genDblRangeUni (l,u) m r | (r==1) && (m > 2) =
let delta = (u / ((fromInteger.toInteger) (m1))) (l / ((fromInteger.toInteger) (m1)))
in take m $ iterate ((+) delta) l
genDblRangeUni (l,u) m r | r==1 && (m <= 2) = [l,u]
genDblRangeUni _ _ _ | otherwise = []
genFltRnd :: StdGen -> Generator Float
genFltRnd s r | r == 1 = (randoms s:: [Float])
genFltRnd _ _ | otherwise = ([] :: [Float])
genFltRangeRnd :: (Float,Float) -> StdGen -> Generator Float
genFltRangeRnd (l,u) s r | r == 1 = (randomRs (l,u) s :: [Float])
genFltRangeRnd _ _ _ | otherwise = ([] :: [Float])
genFltXtrm :: Generator Float
genFltXtrm r | r == 1 =
[minNormal::Float, maxFinite::Float, negate (minNormal::Float), negate (maxFinite::Float), (0::Float),
epsilon::Float, negate (epsilon::Float), infinity::Float, negate (infinity::Float),
nan::Float, (nanWithPayload (maxNaNPayload (1::Float)))::Float, (nanWithPayload 1)::Float]
genFltXtrm _ | otherwise = []
genFltUni :: Int -> Generator Float
genFltUni m r | (r==1) && (m > 2) =
let l = (negate maxFinite::Float)
u = maxFinite::Float
delta = (u / ((fromInteger.toInteger) (m1))) (l / ((fromInteger.toInteger) (m1)))
in take m $ iterate ((+) delta) l
genFltUni m r | r==1 && (m <= 2) = [ (negate maxFinite::Float), maxFinite::Float ]
genFltUni _ _ | otherwise = []
genFltRangeUni :: (Float,Float) -> Int -> Generator Float
genFltRangeUni (l,u) m r | (r==1) && (m > 2) =
let delta = (u / ((fromInteger.toInteger) (m1))) (l / ((fromInteger.toInteger) (m1)))
in take m $ iterate ((+) delta) l
genFltRangeUni (l,u) m r | r==1 && (m <= 2) = [l,u]
genFltRangeUni _ _ _ | otherwise = []
\end{code}