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?
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