我们先把check2注释掉,然后问GHCiprop_2的类型是什么:
*Main> :t prop_2
prop_2 :: Eq a => [a] -> Property
好的,按照您编写 prop_2 的方式,它适用于相等类中任何元素类型的列表。
现在,您想将 prop_2 传递给 quickCheck 函数。接下来看看quickCheck的类型:
*Main> :t quickCheck
quickCheck :: Testable prop => prop -> IO ()
这个函数实际上有一个非常通用的类型。它适用于 Testable 类中的任何内容。那么这个Testable 类是如何工作的呢?这里有几个基本实例,例如:
instance Testable Bool
instance Testable Property -- this is a simplification, but it's more or less true
这些实例在 QuickCheck 库中定义,并告诉您可以快速检查
常量布尔值以及属性类型的元素。
现在,测试不依赖于任何输入的属性并不是特别有趣。有趣的例子是这个:
instance (Arbitrary a, Show a, Testable prop) => Testable (a -> prop)
这就是说,如果您知道如何生成特定类型的随机值 (Arbitrary a) 以及如何显示该类型的值 (Show a),那么您还可以测试来自该类型的函数 @987654335 @ 到一个已经可以测试的类型prop。
为什么?因为这就是 QuickCheck 的运作方式。在这种情况下,QuickCheck 将咨询Arbitrary 实例以提出a 类型的随机测试用例,将函数应用于每个测试用例,并检查结果是否为正。如果任何测试失败,它会打印一条消息,通知您测试失败,并打印测试用例(这就是为什么还有Show 要求)。
现在,在我们的情况下,这意味着我们应该能够快速检查prop_2:它是一个导致Property 的函数。重要的是函数参数(类型为[a],只要Eq a 成立)是类Arbitrary 和类Show 的成员。
这里我们到达了错误的根源。现有信息不足以得出这一结论。正如我一开始所说,prop_2 适用于任何承认相等的元素类型的列表。没有内置规则说所有这些类型都在Arbitrary 和Show 中。但即使有,QuickCheck 应该生成什么样的列表?它应该生成布尔列表、单元类型列表、函数列表、字符列表、整数列表吗?这么多的选项,元素类型的选择很可能会影响到你是否发现了一个bug。 (考虑到 GHC 将选择单元类型 () 仅具有一个元素。那么您的属性将适用于保留输入列表长度的任何两个函数 p 和 q,无论它们是否具有您想要的交换属性与否。)
这就是为什么您需要向 GHC 提供额外的类型信息,以便它可以解析要用于列表的元素类型。这样做很简单。您可以自行注释prop_2:
prop_2 :: [Integer] -> Property
或者,如果您不希望这样(因为您可能希望在不重新实现 prop_2 的情况下在不同类型的列表上运行测试),您可以在调用 quickCheck 时添加类型注释:
check2 = quickCheck (prop_2 :: [Integer] -> Property)
现在代码编译好了,我们可以运行check2:
Main*> check2
+++ OK, passed 100 tests.