Test.SmallCheck.Series

smallcheck-1.1.1: A property-based testing library

Copyright | (c) Colin Runciman et al.
License | BSD3
Maintainer | Roman Cheplyaka <roma@ro-che.info>
Safe Haskell | Trustworthy
Language | Haskell98

Test.SmallCheck.Series

Contents

- Generic instances
- Data Generators
  - What does consN do, exactly?
- Function Generators
  - What does altsN do, exactly?
- Basic definitions
- Convenient wrappers
- Other useful definitions
- Orphan instances

Description

You need this module if you want to generate test values of your own types.

You'll typically need the following extensions:

    {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}

SmallCheck itself defines data generators for all the data types used by the Prelude.

In order to generate values and functions of your own types, you need to make them instances of `Serial` (for values) and `CoSerial` (for functions). There are two main ways to do so: using Generics or writing the instances by hand.

Synopsis

- cons0 :: a -> Series m a
- cons1 :: Serial m a => (a -> b) -> Series m b
- cons2 :: (Serial m a, Serial m b) => (a -> b -> c) -> Series m c
- cons3 :: (Serial m a, Serial m b, Serial m c) => (a -> b -> c -> d) -> Series m d
- cons4 :: (Serial m a, Serial m b, Serial m c, Serial m d) => (a -> b -> c -> d -> e) -> Series m e
- newtypeCons :: Serial m a => (a -> b) -> Series m b
- alts0 :: Series m a -> Series m a
- alts1 :: CoSerial m a => Series m b -> Series m (a -> b)
- alts2 :: (CoSerial m a, CoSerial m b) => Series m c -> Series m (a -> b -> c)
- alts3 :: (CoSerial m a, CoSerial m b, CoSerial m c) => Series m d -> Series m (a -> b -> c -> d)
- alts4 :: (CoSerial m a, CoSerial m b, CoSerial m c, CoSerial m d) => Series m e -> Series m (a -> b -> c -> d -> e)
- newtypeAlts :: CoSerial m a => Series m b -> Series m (a -> b)
- type Depth = Int
- data Series m a
- class Monad m => Serial m a where
- class Monad m => CoSerial m a where
- newtype Positive a = Positive {
  - getPositive :: a
  }
- newtype NonNegative a = NonNegative {
  - getNonNegative :: a
  }
- newtype NonEmpty a = NonEmpty {
  - getNonEmpty :: [a]
  }
- (\/) :: Monad m => Series m a -> Series m a -> Series m a
- (><) :: Monad m => Series m a -> Series m b -> Series m (a, b)
- (<~>) :: Monad m => Series m (a -> b) -> Series m a -> Series m b
- (>>-) :: MonadLogic m => forall a b. m a -> (a -> m b) -> m b
- localDepth :: (Depth -> Depth) -> Series m a -> Series m a
- decDepth :: Series m a -> Series m a
- getDepth :: Series m Depth
- generate :: (Depth -> [a]) -> Series m a
- list :: Depth -> Series Identity a -> [a]
- listM :: Monad m => Depth -> Series m a -> m [a]
- fixDepth :: Series m a -> Series m (Series m a)
- decDepthChecked :: Series m a -> Series m a -> Series m a
- constM :: Monad m => m b -> m (a -> b)

# Generic instances

The easiest way to create the necessary instances is to use GHC generics (available starting with GHC 7.2.1).

Here's a complete example:

    {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
    {-# LANGUAGE DeriveGeneric #-}

    import Test.SmallCheck.Series
    import GHC.Generics

    data Tree a = Null | Fork (Tree a) a (Tree a)
        deriving Generic

    instance Serial m a => Serial m (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.

One minor limitation of generic instances is that there's currently no way to distinguish newtypes and datatypes. Thus, newtype constructors will also count as one level of depth.

# Data Generators

Writing `Serial` instances 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 m a => Serial m (Tree a) where
      series = cons0 Null \/ cons3 Fork

For newtypes use `newtypeCons` instead of `cons1`. The difference is that `cons1` is counts as one level of depth, while `newtypeCons` doesn't affect the depth.

    newtype Light a = Light a

    instance Serial m a => Serial m (Light a) where
      series = newtypeCons Light

For data types with more than 4 fields define `consN` as

    consN f = decDepth $
      f <$> series
        <~> series
        <~> series
        <~> ...    {- series repeated N times in total -}

## What does consN do, exactly?

`consN` has type `(Serial t_1, ..., Serial t_N) => (t_1 -> ... -> t_N -> t) -> Series t`.

`consN f` is a series which, for a given depth `d > 0`, produces values of the form

    f x_1 ... x_N

where `x_i` ranges over all values of type `t_i` of depth up to `d-1` (as defined by the `series` functions for `t_i`).

`consN` functions also ensure that x_i are enumerated in the breadth-first order. Thus, combinations of smaller depth come first (assuming the same is true for `t_i`).

If `d <= 0`, no values are produced.

cons0 :: a -> Series m a #

cons1 :: Serial m a => (a -> b) -> Series m b #

cons2 :: (Serial m a, Serial m b) => (a -> b -> c) -> Series m c #

cons3 :: (Serial m a, Serial m b, Serial m c) => (a -> b -> c -> d) -> Series m d #

cons4 :: (Serial m a, Serial m b, Serial m c, Serial m d) => (a -> b -> c -> d -> e) -> Series m e #

newtypeCons :: Serial m a => (a -> b) -> Series m b #

Same as `cons1`, but preserves the depth.

# Function Generators

To generate functions of an application-specific argument type, make the type an instance of `CoSerial`.

Again there is a standard pattern, this time using the altsN combinators where again N is constructor arity. Here are `Tree` and `Light` instances:

    instance CoSerial m a => CoSerial m (Tree a) where
      coseries rs =
        alts0 rs >>- \z ->
        alts3 rs >>- \f ->
        return $ \t ->
          case t of
            Null -> z
            Fork t1 x t2 -> f t1 x t2

    instance CoSerial m a => CoSerial m (Light a) where
      coseries rs =
        newtypeAlts rs >>- \f ->
        return $ \l ->
          case l of
            Light x -> f x

For data types with more than 4 fields define `altsN` as

    altsN rs = do
      rs <- fixDepth rs
      decDepthChecked
        (constM $ constM $ ... $ constM rs)
        (coseries $ coseries $ ... $ coseries rs)
        {- constM and coseries are repeated N times each -}

## What does altsN do, exactly?

`altsN` has type `(Serial t_1, ..., Serial t_N) => Series t -> Series (t_1 -> ... -> t_N -> t)`.

`altsN s` is a series which, for a given depth `d`, produces functions of type

    t_1 -> ... -> t_N -> t

If `d <= 0`, these are constant functions, one for each value produced by `s`.

If `d > 0`, these functions inspect each of their arguments up to the depth `d-1` (as defined by the `coseries` functions for the corresponding types) and return values produced by `s`. The depth to which the values are enumerated does not depend on the depth of inspection.

alts0 :: Series m a -> Series m a #

alts1 :: CoSerial m a => Series m b -> Series m (a -> b) #

alts2 :: (CoSerial m a, CoSerial m b) => Series m c -> Series m (a -> b -> c) #

alts3 :: (CoSerial m a, CoSerial m b, CoSerial m c) => Series m d -> Series m (a -> b -> c -> d) #

alts4 :: (CoSerial m a, CoSerial m b, CoSerial m c, CoSerial m d) => Series m e -> Series m (a -> b -> c -> d -> e) #

newtypeAlts :: CoSerial m a => Series m b -> Series m (a -> b) #

Same as `alts1`, but preserves the depth.

# Basic definitions

type Depth = Int #

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.

data Series m a #

`Series` is a `MonadLogic` action that enumerates values of a certain type, up to some depth.

The depth bound is tracked in the `SC` monad and can be extracted using `getDepth` and changed using `localDepth`.

To manipulate series at the lowest level you can use its `Monad`, `MonadPlus` and `MonadLogic` instances. This module provides some higher-level combinators which simplify creating series.

A proper `Series` should be monotonic with respect to the depth — i.e. `localDepth (+1) s` should emit all the values that `s` emits (and possibly some more).

It is also desirable that values of smaller depth come before the values of greater depth.

Instances

MonadTrans Series # |
Methods lift :: Monad m => m a -> Series m a #
Monad (Series m) # |
Methods (>>=) :: Series m a -> (a -> Series m b) -> Series m b # (>>) :: Series m a -> Series m b -> Series m b # return :: a -> Series m a # fail :: String -> Series m a #
Functor (Series m) # |
Methods fmap :: (a -> b) -> Series m a -> Series m b # (<$) :: a -> Series m b -> Series m a #
Applicative (Series m) # |
Methods pure :: a -> Series m a # (<*>) :: Series m (a -> b) -> Series m a -> Series m b # (*>) :: Series m a -> Series m b -> Series m b # (<*) :: Series m a -> Series m b -> Series m a #
Alternative (Series m) # |
Methods empty :: Series m a # (<|>) :: Series m a -> Series m a -> Series m a # some :: Series m a -> Series m [a] # many :: Series m a -> Series m [a] #
MonadPlus (Series m) # |
Methods mzero :: Series m a # mplus :: Series m a -> Series m a -> Series m a #
Monad m => MonadLogic (Series m) # |
Methods msplit :: Series m a -> Series m (Maybe (a, Series m a)) # interleave :: Series m a -> Series m a -> Series m a # (>>-) :: Series m a -> (a -> Series m b) -> Series m b # ifte :: Series m a -> (a -> Series m b) -> Series m b -> Series m b # once :: Series m a -> Series m a #

class Monad m => Serial m a where #

Methods

series :: Series m a #

series :: (Generic a, GSerial m (Rep a)) => Series m a #

Instances

Monad m => Serial m Bool # |
Methods series :: Series m Bool #
Monad m => Serial m Char # |
Methods series :: Series m Char #
Monad m => Serial m Double # |
Methods series :: Series m Double #
Monad m => Serial m Float # |
Methods series :: Series m Float #
Monad m => Serial m Integer # |
Methods series :: Series m Integer #
Monad m => Serial m Int # |
Methods series :: Series m Int #
Monad m => Serial m () # |
Methods series :: Series m () #
Serial m a => Serial m (NonEmpty a) # |
Methods series :: Series m (NonEmpty a) #
(Num a, Ord a, Serial m a) => Serial m (NonNegative a) # |
Methods series :: Series m (NonNegative a) #
(Num a, Ord a, Serial m a) => Serial m (Positive a) # |
Methods series :: Series m (Positive a) #
Serial m a => Serial m [a] # |
Methods series :: Series m [a] #
Serial m a => Serial m (Maybe a) # |
Methods series :: Series m (Maybe a) #
(Integral i, Serial m i) => Serial m (Ratio i) # |
Methods series :: Series m (Ratio i) #
(CoSerial m a, Serial m b) => Serial m (a -> b) # |
Methods series :: Series m (a -> b) #
(Serial m a, Serial m b) => Serial m (Either a b) # |
Methods series :: Series m (Either a b) #
(Serial m a, Serial m b) => Serial m (a, b) # |
Methods series :: Series m (a, b) #
(Serial m a, Serial m b, Serial m c) => Serial m (a, b, c) # |
Methods series :: Series m (a, b, c) #
(Serial m a, Serial m b, Serial m c, Serial m d) => Serial m (a, b, c, d) # |
Methods series :: Series m (a, b, c, d) #

class Monad m => CoSerial m a where #

Methods

coseries :: Series m b -> Series m (a -> b) #

A proper `coseries` implementation should pass the depth unchanged to its first argument. Doing otherwise will make enumeration of curried functions non-uniform in their arguments.

coseries :: (Generic a, GCoSerial m (Rep a)) => Series m b -> Series m (a -> b) #

A proper `coseries` implementation should pass the depth unchanged to its first argument. Doing otherwise will make enumeration of curried functions non-uniform in their arguments.

Instances

Monad m => CoSerial m Bool # |
Methods coseries :: Series m b -> Series m (Bool -> b) #
Monad m => CoSerial m Char # |
Methods coseries :: Series m b -> Series m (Char -> b) #
Monad m => CoSerial m Double # |
Methods coseries :: Series m b -> Series m (Double -> b) #
Monad m => CoSerial m Float # |
Methods coseries :: Series m b -> Series m (Float -> b) #
Monad m => CoSerial m Integer # |
Methods coseries :: Series m b -> Series m (Integer -> b) #
Monad m => CoSerial m Int # |
Methods coseries :: Series m b -> Series m (Int -> b) #
Monad m => CoSerial m () # |
Methods coseries :: Series m b -> Series m (() -> b) #
CoSerial m a => CoSerial m [a] # |
Methods coseries :: Series m b -> Series m ([a] -> b) #
CoSerial m a => CoSerial m (Maybe a) # |
Methods coseries :: Series m b -> Series m (Maybe a -> b) #
(Integral i, CoSerial m i) => CoSerial m (Ratio i) # |
Methods coseries :: Series m b -> Series m (Ratio i -> b) #
(Serial m a, CoSerial m a, Serial m b, CoSerial m b) => CoSerial m (a -> b) # |
Methods coseries :: Series m b -> Series m ((a -> b) -> b) #
(CoSerial m a, CoSerial m b) => CoSerial m (Either a b) # |
Methods coseries :: Series m b -> Series m (Either a b -> b) #
(CoSerial m a, CoSerial m b) => CoSerial m (a, b) # |
Methods coseries :: Series m b -> Series m ((a, b) -> b) #
(CoSerial m a, CoSerial m b, CoSerial m c) => CoSerial m (a, b, c) # |
Methods coseries :: Series m b -> Series m ((a, b, c) -> b) #
(CoSerial m a, CoSerial m b, CoSerial m c, CoSerial m d) => CoSerial m (a, b, c, d) # |
Methods coseries :: Series m b -> Series m ((a, b, c, d) -> b) #

# Convenient wrappers

newtype Positive a #

`Positive x`: guarantees that `x > 0`.

Constructors

Positive |
Fields getPositive :: a

Instances

(Num a, Ord a, Serial m a) => Serial m (Positive a) # |
Methods series :: Series m (Positive a) #
Enum a => Enum (Positive a) # |
Methods succ :: Positive a -> Positive a # pred :: Positive a -> Positive a # toEnum :: Int -> Positive a # fromEnum :: Positive a -> Int # enumFrom :: Positive a -> [Positive a] # enumFromThen :: Positive a -> Positive a -> [Positive a] # enumFromTo :: Positive a -> Positive a -> [Positive a] # enumFromThenTo :: Positive a -> Positive a -> Positive a -> [Positive a] #
Eq a => Eq (Positive a) # |
Methods (==) :: Positive a -> Positive a -> Bool # (/=) :: Positive a -> Positive a -> Bool #
Integral a => Integral (Positive a) # |
Methods quot :: Positive a -> Positive a -> Positive a # rem :: Positive a -> Positive a -> Positive a # div :: Positive a -> Positive a -> Positive a # mod :: Positive a -> Positive a -> Positive a # quotRem :: Positive a -> Positive a -> (Positive a, Positive a) # divMod :: Positive a -> Positive a -> (Positive a, Positive a) # toInteger :: Positive a -> Integer #
Num a => Num (Positive a) # |
Methods (+) :: Positive a -> Positive a -> Positive a # (-) :: Positive a -> Positive a -> Positive a # (*) :: Positive a -> Positive a -> Positive a # negate :: Positive a -> Positive a # abs :: Positive a -> Positive a # signum :: Positive a -> Positive a # fromInteger :: Integer -> Positive a #
Ord a => Ord (Positive a) # |
Methods compare :: Positive a -> Positive a -> Ordering # (<) :: Positive a -> Positive a -> Bool # (<=) :: Positive a -> Positive a -> Bool # (>) :: Positive a -> Positive a -> Bool # (>=) :: Positive a -> Positive a -> Bool # max :: Positive a -> Positive a -> Positive a # min :: Positive a -> Positive a -> Positive a #
Real a => Real (Positive a) # |
Methods toRational :: Positive a -> Rational #
Show a => Show (Positive a) # |
Methods showsPrec :: Int -> Positive a -> ShowS # show :: Positive a -> String # showList :: [Positive a] -> ShowS #

newtype NonNegative a #

`NonNegative x`: guarantees that `x >= 0`.

Constructors

NonNegative |
Fields getNonNegative :: a

Instances

(Num a, Ord a, Serial m a) => Serial m (NonNegative a) # |
Methods series :: Series m (NonNegative a) #
Enum a => Enum (NonNegative a) # |
Methods succ :: NonNegative a -> NonNegative a # pred :: NonNegative a -> NonNegative a # toEnum :: Int -> NonNegative a # fromEnum :: NonNegative a -> Int # enumFrom :: NonNegative a -> [NonNegative a] # enumFromThen :: NonNegative a -> NonNegative a -> [NonNegative a] # enumFromTo :: NonNegative a -> NonNegative a -> [NonNegative a] # enumFromThenTo :: NonNegative a -> NonNegative a -> NonNegative a -> [NonNegative a] #
Eq a => Eq (NonNegative a) # |
Methods (==) :: NonNegative a -> NonNegative a -> Bool # (/=) :: NonNegative a -> NonNegative a -> Bool #
Integral a => Integral (NonNegative a) # |
Methods quot :: NonNegative a -> NonNegative a -> NonNegative a # rem :: NonNegative a -> NonNegative a -> NonNegative a # div :: NonNegative a -> NonNegative a -> NonNegative a # mod :: NonNegative a -> NonNegative a -> NonNegative a # quotRem :: NonNegative a -> NonNegative a -> (NonNegative a, NonNegative a) # divMod :: NonNegative a -> NonNegative a -> (NonNegative a, NonNegative a) # toInteger :: NonNegative a -> Integer #
Num a => Num (NonNegative a) # |
Methods (+) :: NonNegative a -> NonNegative a -> NonNegative a # (-) :: NonNegative a -> NonNegative a -> NonNegative a # (*) :: NonNegative a -> NonNegative a -> NonNegative a # negate :: NonNegative a -> NonNegative a # abs :: NonNegative a -> NonNegative a # signum :: NonNegative a -> NonNegative a # fromInteger :: Integer -> NonNegative a #
Ord a => Ord (NonNegative a) # |
Methods compare :: NonNegative a -> NonNegative a -> Ordering # (<) :: NonNegative a -> NonNegative a -> Bool # (<=) :: NonNegative a -> NonNegative a -> Bool # (>) :: NonNegative a -> NonNegative a -> Bool # (>=) :: NonNegative a -> NonNegative a -> Bool # max :: NonNegative a -> NonNegative a -> NonNegative a # min :: NonNegative a -> NonNegative a -> NonNegative a #
Real a => Real (NonNegative a) # |
Methods toRational :: NonNegative a -> Rational #
Show a => Show (NonNegative a) # |
Methods showsPrec :: Int -> NonNegative a -> ShowS # show :: NonNegative a -> String # showList :: [NonNegative a] -> ShowS #

newtype NonEmpty a #

`NonEmpty xs`: guarantees that `xs` is not null

Constructors

NonEmpty |
Fields getNonEmpty :: [a]

Instances

Serial m a => Serial m (NonEmpty a) # |
Methods series :: Series m (NonEmpty a) #
Show a => Show (NonEmpty a) # |
Methods showsPrec :: Int -> NonEmpty a -> ShowS # show :: NonEmpty a -> String # showList :: [NonEmpty a] -> ShowS #

# Other useful definitions

(\/) :: Monad m => Series m a -> Series m a -> Series m a infixr 7 #

Sum (union) of series

(><) :: Monad m => Series m a -> Series m b -> Series m (a, b) infixr 8 #

Product of series

(<~>) :: Monad m => Series m (a -> b) -> Series m a -> Series m b infixl 4 #

Fair version of `ap` and `<*>`

(>>-) :: MonadLogic m => forall a b. m a -> (a -> m b) -> m b #

Fair conjunction. Similarly to the previous function, consider the distributivity law for MonadPlus:

    (mplus a b) >>= k = (a >>= k) `mplus` (b >>= k)

If 'a >>= k' can backtrack arbitrarily many tmes, (b >>= k) may never be considered. (>>-) takes similar care to consider both branches of a disjunctive computation.

localDepth :: (Depth -> Depth) -> Series m a -> Series m a #

Run a series with a modified depth

decDepth :: Series m a -> Series m a #

Run a `Series` with the depth decreased by 1.

If the current depth is less or equal to 0, the result is `mzero`.

getDepth :: Series m Depth #

Query the current depth

generate :: (Depth -> [a]) -> Series m a #

A simple series specified by a function from depth to the list of values up to that depth.

list :: Depth -> Series Identity a -> [a] #

Return the list of values generated by a `Series`. Useful for debugging `Serial` instances.

listM :: Monad m => Depth -> Series m a -> m [a] #

Monadic version of `list`

fixDepth :: Series m a -> Series m (Series m a) #

Fix the depth of a series at the current level. The resulting series will no longer depend on the "ambient" depth.

decDepthChecked :: Series m a -> Series m a -> Series m a #

If the current depth is 0, evaluate the first argument. Otherwise, evaluate the second argument with decremented depth.

constM :: Monad m => m b -> m (a -> b) #

    constM = liftM const

# Orphan instances

(Serial Identity a, Show a, Show b) => Show (a -> b) # |
Methods showsPrec :: Int -> (a -> b) -> ShowS # show :: (a -> b) -> String # showList :: [a -> b] -> ShowS #