Как ограничить QuickCheck при использовании синонимов типов?

Я использую QuickCheck для запуска произвольных тестов в моем коде. Однако в одной части моего кода у меня есть синоним типа:

type Vector = [Double]

У меня также есть несколько функций, которые принимают ряд Vectors в качестве входных данных. Однако все эти функции требуют, чтобы Vector должны быть одинаковой длины.

Есть ли способ ограничить QuickCheck, чтобы он генерировал только списки длиной n?


person sdasdadas    schedule 04.11.2013    source источник


Ответы (4)


Простое решение состоит в том, чтобы не иметь произвольного экземпляра, а вместо этого сделать что-то вроде

import Test.QuickCheck
import Control.Monad

prop_vec :: Int -> Gen [Double]
prop_vec = flip replicateM arbitrary . abs


prop_addComm :: Int -> Gen Bool
prop_addComm i  = do
  v <- prop_vec i
  u <- prop_vec i
  return $ u + v = v + u --assuming you'd added a Num instance for your vectors

Там никогда не бывает типового класса, поэтому вы получаете меньше полезных ошибок, но их проще накрутить.

person Daniel Gratzer    schedule 04.11.2013
comment
Я не имею права судить, какой ответ лучше, поэтому я выбираю этот ответ, потому что искал быстрое решение. - person sdasdadas; 04.11.2013
comment
На самом деле, похоже, требуется довольно много времени, чтобы достичь ограничения в 100 тестов для QuickCheck... Я только складываю векторы вместе, используя zipWith. - person sdasdadas; 05.11.2013
comment
@sdasdadas Попробуйте ограничить размер числа, я могу немного показать пример этого - person Daniel Gratzer; 05.11.2013

Вы можете установить ограничения с помощью нотации ==>.

пример:

prop_test xs = minimum xs == (head $ sort xs)

который терпит неудачу:

*** Failed! Exception: 'Prelude.minimum: empty list' (after 1 test):
[]

теперь с ограничением:

prop_test xs = not (null xs) ==> minimum xs == (head $ sort xs)

оно работает:

*Main> quickCheck prop_test
+++ OK, passed 100 tests.

в твоем случае:

prop_test xs ys = length xs == length ys ==> undefined -- whatever you want
person chamini2    schedule 04.11.2013
comment
Это может быть очень-очень медленным, шансы получить два списка одинакового размера, когда вы генерируете их случайным образом, невелики. - person Daniel Gratzer; 04.11.2013

Другое очевидное решение — создать список кортежей и разархивировать их. Например, в ghci:

> let allSameLength (xs:xss) = all (==length xs) (map length xss)
> quickCheck (\xys -> let (xs, ys) = unzip xys in allSameLength [xs, ys])
+++ OK, passed 100 tests.
> :{
| quickCheck (\wxyzs -> let
|   (wxs, yzs) = unzip wxyzs
|   (ws, xs) = unzip wxs
|   (ys, zs) = unzip yzs
|   in allSameLength [ws, xs, ys, zs])
| :}
+++ OK, passed 100 tests.
person Daniel Wagner    schedule 04.11.2013

Вот одна из возможностей. Мы определим новый класс для типов, которые могут создавать случайные значения, зависящие от размера. Затем вы можете создать список или дерево на уровне типов или что-то еще и объявить один экземпляр Arbitrary для них раз и навсегда.

import Control.Monad
import Test.QuickCheck

class SizedArbitrary a where
    sizedArbitrary :: Int -> Gen a

instance Arbitrary a => SizedArbitrary [a] where
    sizedArbitrary n = replicateM n arbitrary

data Branch a b = a :+ b deriving (Eq, Ord, Show, Read)
instance (SizedArbitrary a, SizedArbitrary b) => SizedArbitrary (Branch a b) where
    sizedArbitrary n = liftM2 (:+) (sizedArbitrary n) (sizedArbitrary n)

instance (SizedArbitrary a, SizedArbitrary b) => Arbitrary (Branch a b) where
    arbitrary = arbitrarySizedIntegral >>= sizedArbitrary . abs

Затем мы можем загрузить его в ghci и проверить, работает ли он:

*Main> let allSameLength (xs:xss) = all (==length xs) (map length xss)
*Main> quickCheck (\(xs :+ ys) -> allSameLength [xs, ys])
+++ OK, passed 100 tests.
*Main> quickCheck (\(ws :+ xs :+ ys :+ zs) -> allSameLength [ws, xs, ys, zs])
+++ OK, passed 100 tests.
person Daniel Wagner    schedule 04.11.2013