I have come across a few instances in my testing with QuickCheck when it would have simplified things to write my own modifiers in some cases, but I'm not exactly sure how one would do this. In particular, it would be helpful to know how to write a modifier for generators of lists and of numerics (such as Int
). I'm aware of NonEmptyList
, and Positive
and NonNegative
, that are already in the library, but in some instances it would have made my tests clearer if I could have specified something like a list that is not only NonEmpty, but also NonSingleton (so, it has at least 2 elements), or an Int
that is greater than 1, not just NonZero
or Positive
, or an Int(egral)
that is even/odd, etc.
There's plenty of way in which you can do that. Here's some examples.
You can write a combinator as a function. Here's one that generates non-singleton lists from any Gen a
:
nonSingleton :: Gen a -> Gen [a]
nonSingleton g = do
x1 <- g
x2 <- g
xs <- listOf g
return $ x1 : x2 : xs
This has the same type as the built-in listOf
function, and can be used in the same way:
useNonSingleton :: Gen Bool
useNonSingleton = do
xs :: [String] <- nonSingleton arbitrary
return $ length xs > 1
Here I've taken advantage of Gen a
being a Monad
so that I could write both the function and the property with do
notation, but you can also write it using monadic combinators if you so prefer.
The function simply generates two values, x1
and x2
, as well as a list xs
of arbitrary size (which can be empty), and creates a list of all three. Since x1
and x2
are guaranteed to be single values, the resulting list will have at least those two values.
Sometimes you just want to throw away a small subset of generated values. You can to that with the built-in ==>
combinator, here used directly in a property:
moreThanOne :: (Ord a, Num a) => Positive a -> Property
moreThanOne (Positive i) = i > 1 ==> i > 1
While this property is tautological, it demonstrates that the predicate you place to the left of ==>
ensures that whatever runs on the right-hand side of ==>
has passed the predicate.
Since Gen a
is a Monad
instance, you can also use existing Monad
, Applicative
, and Functor
combinators. Here's one that turns any number inside of any Functor
into an even number:
evenInt :: (Functor f, Num a) => f a -> f a
evenInt = fmap (* 2)
Notice that this works for any Functor f
, not just for Gen a
. Since, however, Gen a
is a Functor
, you can still use evenInt
:
allIsEven :: Gen Bool
allIsEven = do
i :: Integer <- evenInt arbitrary
return $ even i
The arbitrary
function call here creates an unconstrained Integer
value. evenInt
then makes it even by multiplying it by two.
You can also use newtype
to create your own data containers, and then make them Arbitrary
instances:
newtype Odd a = Odd a deriving (Eq, Ord, Show, Read)
instance (Arbitrary a, Num a) => Arbitrary (Odd a) where
arbitrary = do
i <- arbitrary
return $ Odd $ i * 2 + 1
This also enables you to implement shrink
, if you need it.
You can use the newtype
in a property like this:
allIsOdd :: Integral a => Odd a -> Bool
allIsOdd (Odd i) = odd i
The Arbitrary
instance uses arbitrary
for the type a
to generate an unconstrained value i
, then doubles it and adds one, thereby ensuring that the value is odd.
Take a look at the QuickCheck documentation for many more built-in combinators. I particularly find choose
, elements
, oneof
, and suchThat
useful for expressing additional constraints.