0

I am using QuickCheck to run arbitrary test cases on my code. However, in one portion of my code I have the type synonym:

type Vector = [Double]

I also have a few functions that accept a number of Vectors as input. However, all of these functions require that the Vectors be of the same length.

Is there a way to constrain QuickCheck so that it only generates lists of length n?

recursion.ninja
  • 5,377
  • 7
  • 46
  • 78
sdasdadas
  • 23,917
  • 20
  • 63
  • 148

4 Answers4

3

A simple solution is to not have an arbitrary instance but instead to do something like

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

There's never a typeclass so you get less helpful failures, but it's simpler to whip up.

daniel gratzer
  • 52,833
  • 11
  • 94
  • 134
  • I'm not qualified to judge which answer is best - so I am selecting this answer because I was looking for a quick solution. – sdasdadas Nov 04 '13 at 05:51
  • This actually seems to take quite a long time to hit the 100 test case cap for QuickCheck... I'm only adding the vectors together using `zipWith`. – sdasdadas Nov 05 '13 at 07:24
  • @sdasdadas Try constraining the size of the number, I can show an example of this in a bit – daniel gratzer Nov 05 '13 at 12:47
3

You can set constraints with the ==> notation.

an example is:

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

which fails:

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

now with a constraint:

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

it works:

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

in your case:

prop_test xs ys = length xs == length ys ==> undefined -- whatever you want
chamini2
  • 2,820
  • 2
  • 24
  • 37
  • This can be really really slow though, the chances of coming up with two lists of the same size when your randomly generating them is slim. – daniel gratzer Nov 04 '13 at 04:29
2

The other obvious solution is to generate a list of tuples and unzip them. For example, in 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.
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
1

Here's one possibility. We'll define a new class for types that can build a size-dependent random value. Then you can make a type-level list or tree or whatever and declare one Arbitrary instance for these once and for all.

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

Then we can load it up in ghci and check out that it works:

*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.
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380