Testable functions.
Generate, shrink, and show functions for testing higher-order properties. See README.
Testable functions
A representation of functions for property testing, featuring random generation, shrinking, and printing.
This package implements the core functionality. Separate packages integrate it with existing testing frameworks.
QuickCheck: quickcheck-higherorder
Hedgehog: hedgehog-higherorder
Summary
This package defines a type of testable functionsa :-> r
, representing functions a -> r
.
To interpret a testable function into a function
a -> r
, useapplyFun :: (a :-> r) -> a -> r
.To pretty-print a testable function, use
show :: Show r => (a :-> r) -> String
.To shrink a testable function, given a shrinker for
r
, useshrinkFun :: (r -> [r]) -> (a :-> r) -> [a :-> r]
.To randomly generate a testable function
a :-> r
, apply a cogenerator ofa
to a generator ofr
. Cogenerators can be defined using combinators from this library.
Cogenerators
The type of cogenerators of a
is Co Gen a r
, where Gen
is QuickCheck's monad of random generators and r
is an abstract parameter (it's really forall r. Co Gen a r
).
That type Co Gen a r
is literally defined as a type synonym of Gen r -> Gen (a :-> r)
. Given both a cogenerator c :: Co Gen a r
, and a generator g :: Gen r
, we can construct the generator of testable functions c g :: Gen (a :-> r)
.
(Users can just think of Co Gen
as a whole, even though the implementation defines a more general Co
which may be applied to any monad. Similarly, the parameter r
can be ignored most of the time; it matters to cogenerators of parameterized types.)
There are several combinators to define cogenerators, covering the following scenarios.
Newtypes and embeddings
If we have a newtype A
around some old type B
, and we also have a cogenerator of B
:
newtype A = MkA { unA :: B }
cogenB :: Co Gen B r
Then cogenEmbed
transforms cogenB
into a cogenerator of A
:
cogenEmbed "unA" unA cogenB :: Co Gen A r
This is actually not restricted to newtypes: any "embedding" function A -> B
(here, unA
) can be used to convert a Co Gen B r
to a Co Gen A r
. (Yes, there is a contravariant functor hiding there.) Note that cogenEmbed
expects a name for that function as a String
in its first argument, for pretty-printing.
Generic data types
To define a cogenerator of a type which is an instance of Generic
(from GHC.Generics
), use cogenGeneric
. For example, consider this type:
data Small a = Zero | One a | Two a a
deriving Generic
The function cogenGeneric
takes a heterogeneous list of cogenerators, one for each constructor of the generic type. This is cs
in the example below.
The heterogeneous list is constructed using (:+)
to append elements and ()
for the end of the list.
For constructors with multiple fields, use (.)
to compose cogenerators for individual fields.
For nullary constructors, use id
as the "nullary cogenerator".
cogenSmall ::
forall a.
(forall r. Co Gen a r) ->
(forall r. Co Gen (Small a) r)
cogenSmall cogenA = cogenGeneric cs where
cs
= id -- Nullary cogenerator, for the constructor Zero
:+ cogenA -- Cogenerator of a, for the constructor One
:+ (cogenA . cogenA) -- A cogenerator of a, once for each field of the constructor Two
:+ () -- End of the list
Functions
To generate higher-order testable functions (a -> b) :-> r
, we need a cogenerator of functions a -> b
, which we can define using cogenFun
.
To a first approximation, the function cogenFun
transforms a cogenerator of b
into a cogenerator of (a -> b)
, provided a way to generate, shrink, and show a
.
This is actually generalized further by allowing one to provide a way to generate, shrink, and show a representationa0
of a
, which can be equal to a
in simple cases, but this generalization makes it possible to generate functions of arbitrarily high order.
Hence, to construct a cogenerator of a -> b
, the function cogenFun
takes the following arguments, in this order:
Concrete a0
: a dictionary containing a shrinker and a pretty-printer of representationsa0
;Gen (Maybe a0)
: a random generator ofa0
, it must generateNothing
once in a while (say with probability 1/5 if you have no clue);a0 -> a
: a function from representations to actual values (id
in simple cases);forall r. Co Gen b r
: a cogenerator ofb
.
References
Shrinking and showing functions, by Koen Claessen, Haskell Symposium 2012.
This package extends that work with support for higher-order functions.
Other implementations based on that paper can be found in:
QuickCheck; in particular see the
Fun
type.
Internal module policy
Modules under Test.Fun.Internal
are not subject to any versioning policy. Breaking changes may apply to them at any time.
If something in those modules seems useful, please report it or create a pull request to export it from an external module.