如何在 Haskell 中使用 SmallCheck? [英] How to use SmallCheck in Haskell?
问题描述
我正在尝试使用 SmallCheck 来测试 Haskell 程序,但我不明白如何使用库来测试我自己的数据类型.显然,我需要使用 Test.SmallCheck.Series.但是,我发现它的文档非常混乱.我对食谱风格的解决方案和逻辑(单子?)结构的可理解解释都感兴趣.以下是我的一些问题(所有相关问题):
I am trying to use SmallCheck to test a Haskell program, but I cannot understand how to use the library to test my own data types. Apparently, I need to use the Test.SmallCheck.Series. However, I find the documentation for it extremely confusing. I am interested in both cookbook-style solutions and an understandable explanation of the logical (monadic?) structure. Here are some questions I have (all related):
如果我有一个数据类型
data Person = SnowWhite |Dwarf Integer
,我如何向smallCheck
解释有效值为Dwarf 1
到Dwarf 7
(或SnowWhite代码>)?如果我有一个复杂的
FairyTale
数据结构和一个构造函数makeTale :: [Person] ->FairyTale
,我希望smallCheck
使用构造函数从 Person-s 列表中制作 FairyTale-s?
If I have a data type
data Person = SnowWhite | Dwarf Integer
, how do I explain tosmallCheck
that the valid values areDwarf 1
throughDwarf 7
(orSnowWhite
)? What if I have a complicatedFairyTale
data structure and a constructormakeTale :: [Person] -> FairyTale
, and I wantsmallCheck
to make FairyTale-s from lists of Person-s using the constructor?
通过将 Control.Monad.liftM
明智地应用到 makeTale代码>.我想不出使用
smallCheck
的方法(请给我解释一下!).
I managed to make quickCheck
work like this without getting my hands too dirty by using judicious applications of Control.Monad.liftM
to functions like makeTale
. I couldn't figure out a way to do this with smallCheck
(please explain it to me!).
Serial
、Series
等类型有什么关系?
(可选)coSeries
的意义何在?如何使用 SmallCheck.Series
中的 Positive
类型?
(optional) What is the point of coSeries
? How do I use the Positive
type from SmallCheck.Series
?
(可选)任何关于什么是单子表达式背后的逻辑的说明,以及在 smallCheck 的上下文中什么只是一个常规函数,将不胜感激.
(optional) Any elucidation of what is the logic behind what should be a monadic expression, and what is just a regular function, in the context of smallCheck, would be appreciated.
如果有任何使用 smallCheck
的介绍/教程,我会很感激一个链接.非常感谢!
If there is there any intro/tutorial to using smallCheck
, I'd appreciate a link. Thank you very much!
更新:我应该补充一点,我为 smallCheck
找到的最有用和最易读的文档是 这篇论文 (PDF).乍一看,我找不到问题的答案;它更像是一个有说服力的广告而不是一个教程.
UPDATE: I should add that the most useful and readable documentation I found for smallCheck
is this paper (PDF). I could not find the answer to my questions there on the first look; it is more of a persuasive advertisement than a tutorial.
更新 2: 我提出了关于 Test.SmallCheck.list
类型和其他地方出现的奇怪 Identity
的问题单独的问题一个>.
UPDATE 2: I moved my question about the weird Identity
that shows up in the type of Test.SmallCheck.list
and other places to a separate question.
推荐答案
注意: 此答案描述的是 SmallCheck 的 1.0 之前的版本.请参阅这篇博文了解 SmallCheck 0.6 和 1.0 之间的重要区别.
NOTE: This answer describes pre-1.0 versions of SmallCheck. See this blog post for the important differences between SmallCheck 0.6 and 1.0.
SmallCheck 与 QuickCheck 类似,它在可能类型空间的某些部分上测试属性.不同之处在于它试图详尽地枚举一系列所有小"值,而不是小值的任意子集.
SmallCheck is like QuickCheck in that it tests a property over some part of the space of possible types. The difference is that it tries to exhaustively enumerate a series all of the "small" values instead of an arbitrary subset of smallish values.
正如我所暗示的,SmallCheck 的 Serial
就像 QuickCheck 的 Arbitrary
.
As I hinted, SmallCheck's Serial
is like QuickCheck's Arbitrary
.
现在 Serial
非常简单:一个 Serial
类型 a
有一种方法 (series
) 来生成一个Series
类型,它只是 Depth -> 中的一个函数;[一]代码>.或者,为了解包,
Serial
对象是我们知道如何枚举一些小"值的对象.我们还给出了一个 Depth
参数,该参数控制我们应该生成多少个小值,但让我们暂时忽略它.
Now Serial
is pretty simple: a Serial
type a
has a way (series
) to generate a Series
type which is just a function from Depth -> [a]
. Or, to unpack that, Serial
objects are objects we know how to enumerate some "small" values of. We are also given a Depth
parameter which controls how many small values we should generate, but let's ignore it for a minute.
instance Serial Bool where series _ = [False, True]
instance Serial Char where series _ = "abcdefghijklmnopqrstuvwxyz"
instance Serial a => Serial (Maybe a) where
series d = Nothing : map Just (series d)
在这些情况下,我们只是忽略 Depth
参数,然后枚举每种类型的所有"可能值.我们甚至可以为某些类型自动执行此操作
In these cases we're doing nothing more than ignoring the Depth
parameter and then enumerating "all" possible values for each type. We can even do this automatically for some types
instance (Enum a, Bounded a) => Serial a where series _ = [minBound .. maxBound]
这是一种非常简单的彻底测试属性的方法——从字面上测试每一个可能的输入!显然,至少有两个主要缺陷:(1) 无限数据类型在测试时会导致无限循环;(2) 嵌套类型会导致要查看的示例空间呈指数级增长.在这两种情况下,SmallCheck 都会很快变得非常大.
This is a really simple way of testing properties exhaustively—literally test every single possible input! Obviously there are at least two major pitfalls, though: (1) infinite data types will lead to infinite loops when testing and (2) nested types lead to exponentially larger spaces of examples to look through. In both cases, SmallCheck gets really large really quickly.
这就是 Depth
参数的意义所在——它让系统要求我们保持 Series
小.从文档中,Depth
是
So that's the point of the Depth
parameter—it lets the system ask us to keep our Series
small. From the documentation, Depth
is the
生成测试值的最大深度
对于数据值,是嵌套构造器应用的深度.
For data values, it is the depth of nested constructor applications.
对于函数值来说,既是嵌套案例分析的深度,也是结果的深度.
For functional values, it is both the depth of nested case analysis and the depth of results.
所以让我们重新设计我们的示例以保持它们的小.
so let's rework our examples to keep them Small.
instance Serial Bool where
series 0 = []
series 1 = [False]
series _ = [False, True]
instance Serial Char where
series d = take d "abcdefghijklmnopqrstuvwxyz"
instance Serial a => Serial (Maybe a) where
-- we shrink d by one since we're adding Nothing
series d = Nothing : map Just (series (d-1))
instance (Enum a, Bounded a) => Serial a where series d = take d [minBound .. maxBound]
好多了.
那么什么是coseries
?就像 QuickCheck 的 Arbitrary
类型类中的 coarbitrary
一样,它让我们构建了一系列小"函数.请注意,我们在输入类型上编写实例——结果类型在另一个 Serial
参数中传递给我们(我在下面调用 results
).
So what's coseries
? Like coarbitrary
in the Arbitrary
typeclass of QuickCheck, it lets us build a series of "small" functions. Note that we're writing the instance over the input type---the result type is handed to us in another Serial
argument (that I'm below calling results
).
instance Serial Bool where
coseries results d = [cond -> if cond then r1 else r2 |
r1 <- results d
r2 <- results d]
这些需要更多的聪明才智来编写,我实际上会推荐你使用我将在下面简要描述的 alts
方法.
these take a little more ingenuity to write and I'll actually refer you to use the alts
methods which I'll describe briefly below.
那么我们如何制作一些Series
的Person
?这部分很简单
So how can we make some Series
of Person
s? This part is easy
instance Series Person where
series d = SnowWhite : take (d-1) (map Dwarf [1..7])
...
但是我们的 coseries
函数需要生成从 Person
到其他东西的所有可能的函数.这可以使用 SmallCheck 提供的 altsN
系列函数来完成.这是一种写法
But our coseries
function needs to generate every possible function from Person
s to something else. This can be done using the altsN
series of functions provided by SmallCheck. Here's one way to write it
coseries results d = [person ->
case person of
SnowWhite -> f 0
Dwarf n -> f n
| f <- alts1 results d ]
基本思想是 altsN results
从 N
值生成 Series
的 N
-ary 函数Serial
实例到 Results
的 Serial
实例.所以我们用它从 [0..7] 创建一个函数,一个先前定义的 Serial
值,到我们需要的任何东西,然后我们将 Person
s 映射到数字和把他们传进去.
The basic idea is that altsN results
generates a Series
of N
-ary function from N
values with Serial
instances to the Serial
instance of Results
. So we use it to create a function from [0..7], a previously defined Serial
value, to whatever we need, then we map our Person
s to numbers and pass 'em in.
现在我们有一个 Person
的 Serial
实例,我们可以使用它来构建更复杂的嵌套 Serial
实例.对于实例",如果 FairyTale
是 Person
的列表,我们可以使用 Serial a =>;Serial [a]
实例与我们的 Serial Person
实例一起轻松创建 Serial FairyTale
:
So now that we have a Serial
instance for Person
, we can use it to build more complex nested Serial
instances. For "instance", if FairyTale
is a list of Person
s, we can use the Serial a => Serial [a]
instance alongside our Serial Person
instance to easily create a Serial FairyTale
:
instance Serial FairyTale where
series = map makeFairyTale . series
coseries results = map (makeFairyTale .) . coseries results
((makeFairyTale .)
与 coseries
生成的每个函数组成 makeFairyTale
,这有点混乱)
(the (makeFairyTale .)
composes makeFairyTale
with each function coseries
generates, which is a little confusing)
这篇关于如何在 Haskell 中使用 SmallCheck?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!