testing-feat-1.0.0.0: Test/Feat/Access.hs
-- | Functions for accessing the values of enumerations including
-- compatibility with the property based testing framework QuickCheck
module Test.Feat.Access(
-- * Accessing functions
optimal,
index,
select,
values,
-- * QuickCheck Compatibility
uniform,
-- * Combinators
skipping,
bounded,
sizeRange,
-- * Non-class versions of the access functions
indexWith,
selectWith,
valuesWith,
uniformWith
)where
import Test.Feat.Enumerate
import Control.Enumerable
--import Data.Modifiers
-- base
import Data.List
import Data.Ratio((%))
-- quickcheck
import Test.QuickCheck(choose,Gen)
-- | Memoised enumeration. Note that all cardinalities are kept in memory until your program terminates.
optimal :: Enumerable a => Enumerate a
optimal = global
-- | Index into an enumeration. Mainly used for party tricks (give it a really large number), since usually you want to distinguish values by size.
index :: Enumerable a => Integer -> a
index = indexWith optimal
-- | A more fine grained version of index that takes a size and an
-- index into the values of that size. @select p i@ is only defined
-- for @i@ within bounds (meaning @i < fst (values !! p)@).
select :: Enumerable a => Int -> Index -> a
select = selectWith optimal
{-
-- Not too happy with this phantom argument
countThese :: Enumerable a => a -> Int -> Integer
countThese x k = help x (drop k $ parts optimal) where
help :: a -> [Finite a] -> Integer
help _ [] = 0
help _ (f:_) = fCard f
-}
-- | All values of the enumeration by increasing cost (which is the number
-- of constructors for most types). Also contains the length of each list.
values :: Enumerable a => [(Integer,[a])]
values = valuesWith optimal
-- | Compatibility with QuickCheck. Distribution is uniform generator over
-- values bounded by the given size. Typical use: @sized uniform@.
uniform :: Enumerable a => Int -> Gen a
uniform = uniformWith optimal
-- | Non class version of 'index'.
indexWith :: Enumerate a -> Integer -> a
indexWith e i0 = go (parts e) i0 where
go (Finite crd ix : ps) i = if i < crd then ix i else go ps (i-crd)
go [] _ = error $ "index out of bounds: "++show i0
-- | Non class version of 'select'
selectWith :: Enumerate a -> Int -> Index -> a
selectWith e p i = fIndex (parts e !! p) i
-- | Non class version of 'values'.
valuesWith :: Enumerate a -> [(Integer,[a])]
valuesWith = map fromFinite . parts
-- | Non class version of 'uniform'.
uniformWith :: Enumerate a -> Int -> Gen a
uniformWith = uni . parts where
uni :: [Finite a] -> Int -> Gen a
uni [] _ = error "uniform: empty enumeration"
uni ps maxp = let (incl, rest) = splitAt maxp ps
fin = mconcat incl
in case fCard fin of
0 -> uni rest 1
_ -> do i <- choose (0,fCard fin-1)
return (fIndex fin i)
-- | Enumerates every nth value of the enumeration from a given starting index.
-- As a special case @striped 0 1@ gives all values (starts at index 0 and takes steps of 1).
--
-- Useful for running enumerations in parallel since e.g. @striped 0 2@ is
-- disjoint from @striped 1 2@ and the union of the two cover all values.
skipping :: Enumerate a -> Index -> Integer -> Enumerate a
skipping _ o0 step | step <= 0 || o0 < 0 = error "skippingWith: invalid argument"
skipping e o0 step = fromParts $ go o0 (parts e) where
go o [] = []
go o _ | o < 0 = error "negative"
go o (p:ps) = p' : go o' ps where -- error (show (space,take,o')) :
space = fCard p - o
(take,o') | space <= 0 = (0,o-fCard p)
| space < step = (1,step-space)
| otherwise = (space `quotRem` step)
p' = Finite{fCard = take
, fIndex = \i -> fIndex p (i*step + o)}
-- | A version of values with a limited number of values in each inner list.
-- If the list corresponds to a Part which is larger than the bound it evenly
-- distributes the values across the enumeration of the Part.
bounded :: Enumerate a -> Integer -> Enumerate a
bounded e n = fromParts $ map (samplePart n) $ parts e where
-- The first value is at index 0 and the last value is at index ~= crd - step
-- This is "fair" if we consider using samplePart on the next part as well.
-- An alternative would be to make the last index used |crd-1|.
samplePart :: Index -> Finite a -> Finite a
samplePart m f@(Finite crd ix) =
let step = crd % m
in if crd <= m
then f
else Finite{fCard = m, fIndex = \i -> fIndex f (floor (toRational i * step))}
-- | Remove all sizes exept those in the given inclusive (low,high) range
sizeRange :: Enumerate a -> (Int, Int) -> Enumerate a
sizeRange e (lo, hi) = fromParts $ take (1+hi-lo) $ drop lo $ parts e