-- 3 (find k"th element of a list)
element_at xs x = xs !! x
prop_3a xs x = (x < length xs && x >= 0) ==> element_at xs (x::Int) == (xs !! x::Int)
当 prop_3a 通过 QuickCheck 运行时,它放弃了,因为它不会生成足够长的列表。
如何编写一个生成器来生成长度大于随机整数的列表?
-- 3 (find k"th element of a list)
element_at xs x = xs !! x
prop_3a xs x = (x < length xs && x >= 0) ==> element_at xs (x::Int) == (xs !! x::Int)
当 prop_3a 通过 QuickCheck 运行时,它放弃了,因为它不会生成足够长的列表。
如何编写一个生成器来生成长度大于随机整数的列表?
哈马尔的回答完全可以解决这个问题。但是为了回答确切的问题,我忍不住调查了一下。让我们使用forAll
.
prop_bang x = x >= 0 ==> forAll (listLongerThan x) $ \xs ->
element_at xs x == xs !! x
所以现在我们需要一个函数,listLongerThan :: Int -> Gen [Int]
. 它需要一个长度 x,并生成一个生成器,该生成器将生成长度大于 的列表x
。
listLongerThan :: Int -> Gen [Int]
listLongerThan x = replicateM (x+1) arbitrary
这相当简单:我们只是利用 Monad 的实例Gen
。如果你运行quickCheck prop_bang
,你会注意到它开始花费相当长的时间,因为它开始测试非常长的列表。让我们限制列表的长度,让它走得更快一点。另外,现在listLongerThan
只生成一个x+1
很长的列表;让我们混合一下,再次使用 Gen 的 Monad 实例。
prop_bang =
forAll smallNumber $ \x ->
forAll (listLongerThan x) $ \xs ->
element_at xs x == xs !! x
smallNumber :: Gen Int
smallNumber = fmap ((`mod` 100) . abs) arbitrary
listLongerThan :: Int -> Gen [Int]
listLongerThan x = do
y <- fmap (+1) smallNumber -- y > 0
replicateM (x+y) arbitrary
您可以在 ghci 中使用sample smallNumber
orsample (listLongerThan 3)
来确保它生成了正确的内容。
走另一条路怎么样?首先我们让 QuickCheck 选择一个列表,然后我们限制我们允许的索引。这有效,并且不会丢弃任何测试用例。
prop_3a (NonEmpty xs) = forAll (choose (0, length xs - 1)) $ \i ->
element_at xs i == (xs !! i :: Int)
在这里,我使用forAll
特定的生成器来生成索引,在这种情况下,使用choose
它从指定范围中选择一个元素,并且我还使用类型NonEmptyList
来确保我们不会尝试索引到空列表中。
这有效:
import Test.QuickCheck
element_at :: [a] -> Int -> a
element_at xs i = xs !! i
prop_3a :: [Int] -> Int -> Property
prop_3a xs i = (i >= 0) ==> (length xs > i) ==> element_at xs i == xs !! i
然而,这样做的问题是很多样本值被丢弃了。您可以使用诸如Positive
帮助确保索引有效之类的东西。
如果你想更复杂,你可以使用更多的 newtype 包装器来尝试生成足够长度的值(可能使用sized
,或者一起生成列表和索引:生成列表,然后根据长度生成索引)列表)。