Search code examples
haskellquickcheck

Combining generator for different datatypes in Quickcheck


I would like to combine two custom generators of different data type, but which are grouped together in another datatype.

In the following example, I would like to use the generator for Legumes and AnimalProteins to create another one for Proteins. choose and elements does not work as the type are different. Casting the generators as gen Proteins does not work either.

data AnimalProteins = Beef | Chicken | Fish
data Legumes = WhiteBeans | RedBeans | Lentils | Chickpeas

data Proteins = AnimalProteins | Legumes deriving (Show)

rAnimalProteins :: Gen AnimalProteins
rAnimalProteins = elements [Beef , Chicken , Fish]

rLegumes :: Gen Legumes
rLegumes = elements [WhiteBeans , RedBeans , Lentils , Chickpeas]

-- This does not work !
rProteins :: Gen Proteins
rProteins = choose (rLegumes, rAnimalProteins)

The solution may be simple but I'm quite stuck here as a beginner. Thanks !


Solution

  • Assuming that you intend to model Proteins as a choice between AnimalProteins and Legumes, you probable need something like this instead:

    data AnimalProteins = Beef | Chicken | Fish deriving (Show, Eq)
    data Legumes = WhiteBeans | RedBeans | Lentils | Chickpeas deriving (Show, Eq)
    
    data Proteins = AP AnimalProteins | L Legumes deriving (Show, Eq)
    

    You can combine generators like this:

    rAnimalProteins :: Gen AnimalProteins
    rAnimalProteins = elements [Beef, Chicken, Fish]
    
    rLegumes :: Gen Legumes
    rLegumes = elements [WhiteBeans, RedBeans, Lentils, Chickpeas]
    
    rProteins :: Gen Proteins
    rProteins = oneof [fmap AP rAnimalProteins, fmap L rLegumes]
    

    The rAnimalProteins and rLegumes are as you've already defined them. You can 'lift' them into two separate Gen Proteins values with fmap, since Gen is a Functor.

    For example, fmap AP rAnimalProteins maps the Gen Legumes value to a Gen Proteins value by mapping every generated Legumes value by wrapping it with the AP data constructor. While it's a Gen Proteins value, it'll always create AP values, that is, either AP Beef, AP Chicken, or AP Fish.

    Likewise, fmap L rLegumes lifts rLegumes from Gen Legumes to Gen Proteins. It'll always generate one of the values L WhiteBeans, L RedBeans, L Lentils, or L Chickpeas.

    By using oneof, you compose the two specialised generators to a more complex one that randomly selects one of those generators to generate a value.