让我们首先注释掉check2
并询问 GHCi 的类型prop_2
是什么:
*Main> :t prop_2
prop_2 :: Eq a => [a] -> Property
好的,按照您编写的方式prop_2
,它适用于相等类中任何元素类型的列表。
现在,您要传递prop_2
给该quickCheck
函数。我们来看看quickCheck
next的类型:
*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
),那么您还可以测试从该类型a
到已经可测试的类型的函数prop
。
为什么?因为这就是 QuickCheck 的运作方式。在这种情况下,QuickCheck 将咨询Arbitrary
实例以提出类型的随机测试用例a
,将函数应用于每个测试用例,并检查结果是否为正。如果任何测试失败,它将打印一条消息,通知您测试失败,并打印测试用例(这也是有Show
要求的原因)。
现在,在我们的情况下,这意味着我们应该能够快速检查prop_2
:它是一个导致Property
. 重要的是函数参数(类型[a]
只要Eq a
持有)是 classArbitrary
的成员和 class 的成员Show
。
在这里,我们到达了错误的根源。现有信息不足以得出这一结论。正如我在一开始所说,prop_2
适用于任何承认相等的元素类型的列表。没有内置规则说所有这些类型都在Arbitrary
and中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.