I'm faced with the problem of writing a shrinking function for a generator that depends of the value (s) output by another one. Basically a generator of the form:
do
a <- genA
b <- f a
pure $! g a b
where genA :: Gen a
, f :: a -> Gen b
g :: a -> b -> c
. For the sake of argument assume g = (,)
. Then the problem is that given a pair (a, b)
, shrinking a
could break the relation that exists between a
, f
and b
.
To give an example of this, consider the following lists with a cached length:
data List =
List
{ llength :: Int
, list :: [Int]
} deriving (Eq, Show)
The arbitrary
function can be easily defined:
instance Arbitrary List where
arbitrary = do
n <- choose (0, 10)
xs <- vector n
pure $! List { llength = n, list = xs }
however shrinking an element of the form List n xs
requires some knowledge of the relation between n
and xs
.
In the preceding example this isn't a problem, since the relation between n
and xs
is easy to determine, however for the problem I'm working on determining this relationship is not trivial.
Libraries like hedgehog
that feature integrated shrinking would solve this particular problem (albeit not always), however I'm wondering if there is a principled way to solve this in QuickCheck
.
Here is an example that shows how hedgehog
finds the minimal counterexample for the property that the length of the list is less than 5 (it consistently gives List 5 [ 0 , 0 , 0 , 0 , 0 ]
as counterexample). And here is an example of how I (think I) solved this problem at the moment, basically by emulating integrated shrinking.
Also, be aware that the the behavior of the monadic interface of hedgehog
is probably not what you want most of the time.