TimeOfDay 的任意实例

Arbitrary instance for TimeOfDay

使用 QuickCheck,我想创建一系列伪随机 TimeOfDay 值。

很容易创建特定的 TimeOfDay:

now = TimeOfDay 17 35 22

使用 GHCi 8.6.5 打印此结果:

17:35:22

我认为使用 QuickCheck 创建 TimeOfDay 值所需的 Arbitrary 实例是:

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

尽管此类型检查,运行 以下行挂起 GHCi 并在几秒钟后将 Killed 写入控制台:

sample (arbitrary :: Gen TimeOfDay)

错误在哪里?

错误的原因是 TimeOfDay 的最后一个构造函数参数是具有 picosecond 分辨率的秒数。

我假设因此,sec <- elements [0 .. 60] 不是生成 61 个值之一,而是生成 61 * 10^2 个值之一。

这解决了问题:

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

现在,sample (arbitrary :: Gen TimeOfDay) 生成例如:

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

如果您还需要亚秒范围内的值,请更改列表生成器的步长:

picoSecond <- elements [1, 1.1 .. 60]

示例值:

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

如您所见,todSeconds 的类型为 Pico,它是一个定点数,分辨率为 10-12,所以这意味着 [0 .. 60]6×1013+1 值。这很容易需要 ~1000 秒来遍历整个列表。

话虽如此,您一开始就不需要在这里使用 elements。我们可以使用 choose :: Random a => (a, a) -> Gen a 来生成一个范围内的随机值(包括两个范围)。

然后我们可以将 Arbitrary 定义为:

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

然后我们得到:

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

如果你不想要这些皮秒,我们可以在fmap中做乘法:

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

然后我们得到:

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