Search code examples
haskelltimequickcheck

Arbitrary instance for TimeOfDay


Using QuickCheck, I'd like to create a series of pseudorandom TimeOfDay values.

It's easy to create a specific TimeOfDay:

now = TimeOfDay 17 35 22

Printing this with GHCi 8.6.5 yields:

17:35:22

I thought that the Arbitrary instance necessary for creating TimeOfDay values with QuickCheck would thus be:

instance Arbitrary TimeOfDay where
  arbitrary = do
    hour <- elements [0 .. 23]
    min  <- elements [0 .. 59]
    -- Going till 60 accounts for leap seconds
    sec  <- elements [0 .. 60]
    return $ TimeOfDay hour min sec

Although this typechecks, running the following line hangs GHCi and after a couple of seconds writes Killed to the console:

sample (arbitrary :: Gen TimeOfDay)

Where's the bug?


Solution

  • As you found out, the todSeconds has as type Pico which is a fixed-point number with a resolution of 10-12, so that means that [0 .. 60] has 6×1013+1 values. This will easily take ~1000 seconds to iterate over the entire list.

    That being said, you do not need to use elements here in the first place. We can use choose :: Random a => (a, a) -> Gen a that will generate a random value within bounds (both bounds inclusive).

    We can then define our Arbitrary as:

    instance Arbitrary TimeOfDay where
        arbitrary = TimeOfDay
            <$> choose (0, 23)
            <*> choose (0, 59)
            <*> (fmap MkFixed (choose (0, 61*10^12-1)))
    

    This then gives us:

    Main> sample (arbitrary :: Gen TimeOfDay)
    15:45:04.132804129488
    11:06:12.447614162981
    12:07:50.773642440667
    04:40:47.966398431784
    02:30:09.60931551059
    00:51:46.564756092467
    07:57:44.170698241052
    02:45:57.743854623407
    00:17:22.627238967351
    13:03:57.364852826473
    11:12:34.894890974241
    

    If you do not want these picoseconds, we can do the multiplication in the fmap:

    instance Arbitrary TimeOfDay where
        arbitrary = TimeOfDay
            <$> choose (0, 23)
            <*> choose (0, 59)
            <*> (fmap (MkFixed . (10^12 *)) (choose (0, 60)))
    

    Then we obtain:

    Main> sample (arbitrary :: Gen TimeOfDay)
    15:00:53
    14:02:44
    14:44:40
    12:40:12
    09:55:39
    10:06:02
    15:00:51
    15:52:23
    16:59:05
    22:38:45
    20:23:15