making an Arbitrary instance for a complicated type

Viewed 208

I'm trying to check my code using QuickCheck so I need to make Arbitrary instances for my types. One type data Schedule is complicated, and I don't know how to treat it properly. Three instances (for Date, Activity, TimeStart) work fine. But the last instance (for Schedule) is incorrect, and I seek for help with fixing it.

data Schedule = Schedule [(Date,[Activity])]
data Date = Date Year Month Day
data Activity = Activity (ActivityName,TimeStart)
data TimeStart = TimeStart Hour Minute

type Year = Integer
type Month = Integer
type Day = Integer
type ActivityName = String
type Hour = Integer
type Minute = Integer

instance Arbitrary Date where
  arbitrary = do
    year <- choose (2000, 2050)
    month <- choose (1, 12)
    day <- choose (1, 28)
    return $ Date year month day

instance Arbitrary Activity where
  arbitrary = do
    size <- choose (5, 15 :: Integer)
    activityName <- sequence [elements ['a'..'z'] | _ <- [1..size]]
    timeStart <- arbitrary
    return $ Activity (activityName, timeStart)

instance Arbitrary TimeStart where
  arbitrary = do
    hour <- choose (1, 24)
    minute <- choose (1, 60)
    return $ TimeStart hour minute

instance Arbitrary Schedule where
  arbitrary = do
    size <- choose (5, 50 :: Integer)
    schedule <- sequence [(arbitrary, arbitraryActivity) | _ <- [1..size]]
    return $ Schedule schedule where
      arbitraryActivity = do
        size <- choose (5, 50 :: Integer)
        activityName <- sequence [arbitrary | _ <- [1..size]]
        return $ ActivityName activityname

First error is in this line: schedule <- sequence [(arbitrary, arbitraryActivity) | _ <- [1..size]] Expected type: Gen [Gen b0] Actual type: (Gen a0, [Gen b0]) But how to make an arbitrary for such a construction [(x,y)] properly?

Second error is in the last line: return $ ActivityName activityname Data constructor not in scope: ActivityName Is it not allowed to use where in do-notation the way I did?

1 Answers

When you already have Arbitrary instances for all the constituent types of a complex type (in this case Date, Activity, etc.), you don't have to do much.

Due to built-in Arbitrary instances of lists, tuples, etc., you can simply define the composed Arbitrary instance like this:

instance Arbitrary Schedule where
  arbitrary = Schedule <$> arbitrary

The compiler looks at the Schedule data constructor, which has the type [(Date, [Activity])] -> Schedule. It infers, then, that the right-hand arbitrary must have the type Gen (Date, [Activity]), i.e. a tuple. Does an Arbitrary instance exist for (a, b)?

Yes, it does: (Arbitrary a, Arbitrary b) => Arbitrary (a, b). It exists if a is an Arbitrary instance, and if b is.

In this case, a ~ Date, and you've defined a custom instance for that. The second tuple element is a bit more complex because it's b ~ [Activity]. Is there an Arbitrary instance for [Activity]?

Yes, again built-in: Arbitrary a => Arbitrary [a]. Lists have Arbitrary instances if the element type has an instance, and again, a custom instance exists for Activity.


It looks as though you're explicitly trying to constrain the sizes of generated values. Instead of doing this explicitly, you can use the resize function.

Related