How can I constraint QuickCheck parameters, e.g. only use non-negative ints?
Asked Answered
C

3

7

I'm new to Haskell. It's very nice so far, but I'm running into copy-pasting for my QuickCheck properties, and I'd like to fix that.

Here's a made-up example:

prop_Myfunc :: [Int] -> (Int,Int) -> Bool
prop_Myfunc ints (i,j) = ints !! i == ints !! j

This won't work because QuickCheck generates negative numbers, so I get

*** Failed! (after 2 tests and 2 shrinks):                               
Exception:
  Prelude.(!!): negative index

I've tried to google for solutions to this, and I've found e.g. NonNegative and ==>, but I don't understand how they work.

How can I restrict the above example so that i and j are never negative? And also, so that neither is too high? That is: 0 <= i,j < length ints

Coorg answered 27/11, 2014 at 13:34 Comment(2)
I think you mean 0 <= i,j < length intsSelfsustaining
@user5402 Yes, thank you! I'll change that. Will check your answer as soon as I can, it looks very good.Coorg
C
6

Constraining wrappers (from Test.QuickCheck.Modifiers, if they aren't reexported implicitly) can be used in this way:

prop_Myfunc :: [Int] -> (NonNegative Int, NonNegative Int) -> Bool
prop_Myfunc ints (NonNegative i, NonNegative j) = ints !! i == ints !! j

You can treat SomeWrapper a as a with modified distribution. For example, NonNegative a ensures that a >= 0. After the wrapper was generated, the value can be get with pattern-matching or explicit accessor (getNonNegative in this case).

As for constraining the top margin of your indices, I think it's not possible with wrappers (it's impossible in the Haskkell type system to parameterise a type with the value, the list length in this case). However, with the ==> operator you can add an arbitrary boolean constraint for your test:

prop_Myfunc ints (NonNegative i, NonNegative j) = i < l && j < l ==> ints !! i == ints !! j where
    l = length ints

It works in other way: when the condition isn't true, it simply discards the current test case. But be careful: if there are too many thrown cases (the condition is too restrictive), the test becomes much less useful. A „lossless“ behaviour can be often achieved with shrinking test data, but it's a whole other topic.

Contrastive answered 27/11, 2014 at 13:57 Comment(1)
Thank you! This is precisely what I needed.Coorg
S
9

First, see this SO answer for an example of how to write a custom Gen ... function and how to use the forAll combinator.

And here is how to write a generator for a non-empty list and two valid non-negative indices into the list:

import Test.QuickCheck

genArgs :: Gen ( [Int], Int, Int )
genArgs = do
  x <- arbitrary
  xs <- arbitrary
  let n = length xs
  i <- choose (0,n)
  j <- choose (0,n)
  return ( (x:xs), i, j) -- return a non-empty list

test = quickCheck $ forAll genArgs $ \(xs,i,j) -> prop_myfunc xs (i,j)
Selfsustaining answered 27/11, 2014 at 17:57 Comment(2)
Thank you very much. I learnt a lot from this. Yuuri's answer was more in line with what I wanted, but I wish I could accept. Have an upvote, at least.Coorg
In fact, I like this solution more than my own :)Contrastive
C
6

Constraining wrappers (from Test.QuickCheck.Modifiers, if they aren't reexported implicitly) can be used in this way:

prop_Myfunc :: [Int] -> (NonNegative Int, NonNegative Int) -> Bool
prop_Myfunc ints (NonNegative i, NonNegative j) = ints !! i == ints !! j

You can treat SomeWrapper a as a with modified distribution. For example, NonNegative a ensures that a >= 0. After the wrapper was generated, the value can be get with pattern-matching or explicit accessor (getNonNegative in this case).

As for constraining the top margin of your indices, I think it's not possible with wrappers (it's impossible in the Haskkell type system to parameterise a type with the value, the list length in this case). However, with the ==> operator you can add an arbitrary boolean constraint for your test:

prop_Myfunc ints (NonNegative i, NonNegative j) = i < l && j < l ==> ints !! i == ints !! j where
    l = length ints

It works in other way: when the condition isn't true, it simply discards the current test case. But be careful: if there are too many thrown cases (the condition is too restrictive), the test becomes much less useful. A „lossless“ behaviour can be often achieved with shrinking test data, but it's a whole other topic.

Contrastive answered 27/11, 2014 at 13:57 Comment(1)
Thank you! This is precisely what I needed.Coorg
M
1

I was in a similar situation as you and I finally found how to use ==> here: http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html, in the "Conditional Properties" section.

With your example, you'd have to replace Bool with Property and insert the requirements about your variables before the property test, as follows:

prop_Myfunc :: [Int] -> (Int,Int) -> Property
prop_Myfunc ints (i,j) = (i >= 0 && j >= 0) ==> ints !! i == ints !! j

(I haven't tested on this particular example, but it worked for me on a similar case.)

Note the type of ==>: (==>) :: Testable prop => Bool -> prop -> Property.

Myrilla answered 5/8, 2016 at 13:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.