1
votes

I would like to generate random terms based on some sort of "context" and I was wondering if this is possible using quickcheck. Basically I would like to have an additional data type passed around so that the arbitrary function can generate terms based on the additional parameter... Is this possible with quickcheck or should I just write my own definition of Gen?

2

2 Answers

2
votes

It's possible, though not really sane, to do this from within arbitrary. But if you step out of arbitrary, you can literally just pass an extra parameter around.

-- do whatever you want inside the implementation of these two
chooseIntRange :: Context -> Int
updateContext :: Int -> Context -> Context

arbitraryIntWithContext :: Context -> Gen (Context, Int)
arbitraryIntWithContext ctx = do
    n <- choose (0, chooseIntRange ctx)
    return (n, updateContext n ctx)

The plumbing of the context can be relieved somewhat with StateT, e.g.

-- do whatever you want inside the implementation of this
chooseIntRangeAndUpdate :: MonadState Context m => m Int

arbitraryIntStateT :: StateT Context Gen Int
arbitraryIntStateT = do
    hi <- chooseIntRangeAndUpdate
    lift (choose (0, hi))
1
votes

While Daniel Wagner has supplied a fine answer for QuickCheck (+1), it also highlights one of QuickCheck's weaknesses. In QuickCheck, one writes properties using instances of Arbitrary, but due to its design, Arbitrary isn't monadic.

The workaround that Daniel Wagner shares is that Gen, on the other hand, is monadic, so that you can write context-dependent code using do notation. The disadvantage is that while you can convert a Gen a to an Arbitrary a, you'll either have to provide a custom shrink implementation, or forgo shrinking.

An alternative library for property-based testing, Hedgehog, is designed in such a way that properties themselves are monadic, which means you'd be able to write an entire property and simply embed ad-hoc context-specific value generation (including shrinking) in the test code itself:

propWithContext :: Property
propWithContext = property $ do
  ctx <- forAll genContext
  n <- forAll $ Gen.integral $ Range.linear 0 $ chooseIntRange ctx
  let ctx' = updateContext n ctx

  -- Exercise SUT and verify result here...

Here, genContext is a custom generator for the Context type, with the type

genContext :: MonadGen m => m Context