\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 Data.Ratio (Ratio, (%)) import Numeric.IEEE import Test.GenCheck.Base.Base (Rank) import Test.GenCheck.Generator.BaseEnum -- enum* base enumerations 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) (r-1) 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) (m-1))) - (l / ((fromInteger.toInteger) (m-1))) 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) (m-1))) - (l / ((fromInteger.toInteger) (m-1))) 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) (m-1))) - (l / ((fromInteger.toInteger) (m-1))) 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) (m-1))) - (l / ((fromInteger.toInteger) (m-1))) in take m $ iterate ((+) delta) l genFltRangeUni (l,u) m r | r==1 && (m <= 2) = [l,u] genFltRangeUni _ _ _ | otherwise = [] \end{code}