Search code examples
haskellquickcheckfrege

QuickCheck: How to combine two generators?


I have two generators, gen_n & gen_arr:

gen_n :: Gen Int
gen_n = suchThat arbitrary (\i -> i >= 0 && i <= 10)

gen_elem :: Gen Int
gen_elem = suchThat arbitrary (\i -> i >= 0 && i <= 100)

gen_arr :: Gen [Int]
gen_arr = listOf gen_elem 

How can I combine these two into a Gen (Int, [Int])?

combine_two_gens :: Gen a -> Gen b -> Gen (a, b)

Solution

  • (i) You can use normal functorial/monadic composition to combine them:

    gen_comb :: Gen (Int, [Int])
    gen_comb = (,) <$> gen_elem <*> gen_arr
    

    (Control.Applicative.liftA2 and Control.Monad.liftM2 are also fine, of course)

    (ii) don't use suchThat to merely constrain a range. It can be terribly inefficient, as it just generates random instances until the condition is met, discarding the rest. Instead, you could use elements :: [a] -> Gen a:

    gen_elem' :: Gen Int
    gen_elem' = elements [0..100]
    
    gen_arr' :: Gen [Int]
    gen_arr' = listOf gen_elem'
    
    gen_comb' :: Gen (Int, [Int])
    gen_comb' = (,) <$> elements [0..100] <*> listOf (elements [0..100])
    

    Update: As Zeta remarked below, we can do even better in this case, by using choose (0,100) (choose :: Random a => (a, a) -> Gen a) instead of elements [0..100]. See here, or here for the full list of generator combinators.


    *Main> sample gen_arr'
    [78]
    [2,27]
    [12,39]
    [92,22,40,6,18,19,25,13,95,99]
    ...
    *Main> sample gen_comb'
    (9,[23,3])
    (11,[67,38,11,79])
    (5,[96,69,68,81,75,14,59,68])
    ...
    

    suchThat vs. elements:

    *Main> sample (suchThat arbitrary (\i -> i >= 10000 && i <= 10005))
    ^CInterrupted.
    *Main> sample (elements [10000..10005])
    10003
    10002
    10000
    10000
    ...
    

    The suchThat generator didn't output anything.