简体   繁体   中英

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 !

The problem is that your Proteins data type doesn't actually contain any AnimalProteins or Legumes ! Yes, you've named the constructors of Proteins the same way, but constructors live in the term language, not in the type language, so the compiler doesn't automatically associate them to the AnimalProteins type .

To do that, you need to be explicit:

data Proteins = AnimalProteins AnimalProteins | Legumes Legumes

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM