2
votes

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
Note that it's highly recommended to always also define shrink in addition to arbitrary.leftaroundabout

1 Answers

4
votes

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.