1

我在使用 QuickCheck 的测试中遇到了一些实例,在某些情况下它可以简化编写我自己的修饰符的事情,但我不确定如何做到这一点。特别是,了解如何为列表和数字的生成器(例如Int)编写修饰符会很有帮助。我知道NonEmptyList, 和Positive,NonNegative已经在库中,但在某些情况下,如果我可以指定类似列表的东西,它不仅是 NonEmpty,而且是 NonSingleton(所以,它有至少 2 个元素),或Int大于 1 的一个,不只是NonZeroPositive,或一个Int(egral)偶数/奇数等。

4

1 回答 1

3

有很多方法可以做到这一点。这里有一些例子。

组合函数

您可以将组合器编写为函数。这是一个从 any 生成非单例列表的列表Gen a

nonSingleton :: Gen a -> Gen [a]
nonSingleton g = do
  x1 <- g
  x2 <- g
  xs <- listOf g
  return $ x1 : x2 : xs

这与内置listOf函数具有相同的类型,并且可以以相同的方式使用:

useNonSingleton :: Gen Bool
useNonSingleton = do
  xs :: [String] <- nonSingleton arbitrary
  return $ length xs > 1

在这里,我利用了Gen a作为 a 的优势,Monad这样我就可以用do符号编写函数和属性,但是如果您愿意,也可以使用单子组合器来编写它。

该函数只生成两个值x1x2,以及一个xs任意大小的列表(可以为空),并创建一个包含所有三个值的列表。由于x1x2保证是单个值,因此结果列表将至少具有这两个值。

过滤

有时您只想丢弃一小部分生成的值。您可以使用内置==>组合器来实现这一点,这里直接在属性中使用:

moreThanOne :: (Ord a, Num a) => Positive a -> Property
moreThanOne (Positive i) = i > 1 ==> i > 1

虽然此属性是重言式的,但它表明您放置在左侧的谓词==>确保在右侧运行的任何内容==>都通过了谓词。

现有的一元组合器

由于Gen a是一个Monad实例,您还可以使用现有MonadApplicative、 和Functor组合符。这是一个将any中的任何数字Functor变成偶数的方法:

evenInt :: (Functor f, Num a) => f a -> f a
evenInt = fmap (* 2)

请注意,这适用于any Functor f,而不仅仅是Gen a. 但是,由于Gen a是 a Functor,您仍然可以使用evenInt

allIsEven :: Gen Bool
allIsEven = do
  i :: Integer <- evenInt arbitrary
  return $ even i

此处的arbitrary函数调用创建了一个不受约束的Integer值。evenInt然后将其乘以 2。

任意新类型

您还可以使用newtype创建自己的数据容器,然后将它们创建为Arbitrary实例:

newtype Odd a = Odd a deriving (Eq, Ord, Show, Read)

instance (Arbitrary a, Num a) => Arbitrary (Odd a) where
  arbitrary = do
    i <- arbitrary
    return $ Odd $ i * 2 + 1

shrink如果需要,这也使您能够实施。

您可以newtype在这样的属性中使用:

allIsOdd :: Integral a => Odd a -> Bool
allIsOdd (Odd i) = odd i

Arbitrary实例使用arbitrary该类型a生成一个不受约束的值i,然后将其加倍并加一,从而确保该值是奇数。

查看更多内置组合器的QuickCheck 文档。我特别发现choose, elements, oneof,suchThat对表达额外的约束很有用。

于 2019-06-04T06:51:34.907 回答