While writing some Arbitrary
instances, I implemented a couple of functions with the following quite mechanical pattern:
type A = Arbitrary -- to cut down on the size of the annotations below
shrink1 :: (A a ) => (a -> r) -> (a -> [r])
shrink2 :: (A a, A b ) => (a -> b -> r) -> (a -> b -> [r])
shrink3 :: (A a, A b, A c) => (a -> b -> c -> r) -> (a -> b -> c -> [r])
shrink1 f a = [f a' | a' <- shrink a]
shrink2 f a b = [f a' b | a' <- shrink a] ++ [f a b' | b' <- shrink b]
shrink3 f a b c = [f a' b c | a' <- shrink a] ++ [f a b' c | b' <- shrink b] ++ [f a b c' | c' <- shrink c]
I wrote out these functions by hand up to shrink7
, and that seems to be sufficient for my needs. But I can't help but wonder: can this reasonably be automated? Bonus points for a solution that:
- allows for
shrink0 f = []
- generates all the shrinkers
- has loads of typeclass hackery, I love that
- skips the scary extensions like incoherent/undecidable/overlapping instances
- lets me have my cake and eat it, too: doesn't require me to uncurry
f
when passing it in or curry the applicationshrinkX f
when applying it toa
,b
, andc