您如何在QuickCheck中编写新的修饰符

时间:2019-06-04 04:42:43

标签: haskell quickcheck property-testing

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

1 个答案:

答案 0 :(得分:2)

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

组合器功能

您可以将组合器编写为函数。这是一个从任何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作为Monad的优势,以便可以使用do表示法来编写函数和属性,但也可以使用单子组合器来编写它如果您愿意的话。

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

过滤

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

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

虽然此属性是重言式的,但它表明您放置在==>左侧的谓词可确保==>右侧运行的所有谓词都已通过该谓词。

现有的单子组合器

由于Gen aMonad实例,因此您也可以使用现有的MonadApplicativeFunctor组合器。这是一个将Functor中的任何数字转换为偶数的数字:

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

请注意,这适用于 any Functor f,而不仅仅是Gen a。但是,由于Gen aFunctor,因此您仍然可以使用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 documentation,了解更多内置组合器。我特别发现chooseelementsoneofsuchThat对于表达附加约束很有用。