如何在 Haskell 中使用 SmallCheck? [英] How to use SmallCheck in Haskell?

查看:24
本文介绍了如何在 Haskell 中使用 SmallCheck?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 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 1Dwarf 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 to smallCheck that the valid values are Dwarf 1 through Dwarf 7 (or SnowWhite)? What if I have a complicated FairyTale data structure and a constructor makeTale :: [Person] -> FairyTale, and I want smallCheck 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!).

SerialSeries等类型有什么关系?

(可选)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.

那么我们如何制作一些SeriesPerson?这部分很简单

So how can we make some Series of Persons? 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 Persons 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 resultsN 值生成 SeriesN-ary 函数Serial 实例到 ResultsSerial 实例.所以我们用它从 [0..7] 创建一个函数,一个先前定义的 Serial 值,到我们需要的任何东西,然后我们将 Persons 映射到数字和把他们传进去.

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 Persons to numbers and pass 'em in.

现在我们有一个 PersonSerial 实例,我们可以使用它来构建更复杂的嵌套 Serial 实例.对于实例",如果 FairyTalePerson 的列表,我们可以使用 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 Persons, 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆