我在使用 QuickCheck 的测试中遇到了一些实例,在某些情况下它可以简化编写我自己的修饰符的事情,但我不确定如何做到这一点。特别是,了解如何为列表和数字的生成器(例如Int
)编写修饰符会很有帮助。我知道NonEmptyList
, 和Positive
,NonNegative
已经在库中,但在某些情况下,如果我可以指定类似列表的东西,它不仅是 NonEmpty,而且是 NonSingleton(所以,它有至少 2 个元素),或Int
大于 1 的一个,不只是NonZero
或Positive
,或一个Int(egral)
偶数/奇数等。
1 回答
有很多方法可以做到这一点。这里有一些例子。
组合函数
您可以将组合器编写为函数。这是一个从 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
符号编写函数和属性,但是如果您愿意,也可以使用单子组合器来编写它。
该函数只生成两个值x1
和x2
,以及一个xs
任意大小的列表(可以为空),并创建一个包含所有三个值的列表。由于x1
和x2
保证是单个值,因此结果列表将至少具有这两个值。
过滤
有时您只想丢弃一小部分生成的值。您可以使用内置==>
组合器来实现这一点,这里直接在属性中使用:
moreThanOne :: (Ord a, Num a) => Positive a -> Property
moreThanOne (Positive i) = i > 1 ==> i > 1
虽然此属性是重言式的,但它表明您放置在左侧的谓词==>
确保在右侧运行的任何内容==>
都通过了谓词。
现有的一元组合器
由于Gen a
是一个Monad
实例,您还可以使用现有Monad
的Applicative
、 和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
对表达额外的约束很有用。