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
The reason for the bug is that TimeOfDay
's last constructor parameter is the number of seconds with picosecond resolution.
I assume that consequently, sec <- elements [0 .. 60]
wasn't generating one of 61 values but one of 61 * 10¹² values.
This fixes the issue:
instance Arbitrary TimeOfDay where
arbitrary = do
hour <- elements [0 .. 23]
min <- elements [0 .. 59]
-- Going till 60 accounts for leap seconds
picoSecond <- elements [1, 2 .. 60]
return $ TimeOfDay hour min picoSecond
Now, sample (arbitrary :: Gen TimeOfDay)
produces for example:
17:19:03
15:49:58
01:28:40
13:20:07
12:00:01
12:35:45
13:25:33
12:55:20
07:11:54
21:10:46
14:34:15
If you also want values in the subsecond range, change the step width of the list generator:
picoSecond <- elements [1, 1.1 .. 60]
Sample values:
04:09:23.6
10:22:50.4
18:56:57.6
07:12:07.8
08:12:02.6
14:20:40
05:42:21.4
14:35:42.3
02:47:32.6
22:02:26.2
08:26:09.4
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With