3

I want to write a property like the following:

(prop/for-all [x (gen/nat)
               y (gen/nat)]
  (= (g x y) (f x y)))

However, the property only holds when x > y. What is the correct way to express that precondition for this property? (Better yet, how can I write this property such that y is generated as a natural number less than x?)

Dan Burton
  • 53,238
  • 27
  • 117
  • 198

2 Answers2

5
  1. You could generate y and an intermediate number dy, then compute x as (+ y dy).

    Generating dy using clojure.test.check.generators/nat ensures that it will be nonnegative – no need to apply absolute value in user code. If x needs to be strictly greater than – and not equal to – y, use clojure.test.check.generators/pos-int to generate dy instead.

    I believe this will tend to treat cases where the two numbers are closer together as "simpler" for the purposes of generating minimal failing cases. It seems that that would be a useful property for many scenarios – you'll have to judge if it's ok for yours.

  2. You could generate x and y independently and use rejection sampling – clojure.test.check.generators/such-that allows you to "filter" the values generated by a base generator with a predicate of your choice.

    This isn't a great approach when the case you're looking for is generated with a very low probability, but x will be greater than y in ~½ of all cases, so it should be fine here.

  3. You could use clojure.test.check.generators/bind as suggested by Mike. I'd suggest using it in tandem with clojure.test.check.generators/choose to generate a positive x and then a y in the range [0…x-1], perhaps in the following way:

    (prop/for-all [[x y] (gen/bind gen/nat
                           (fn [v]
                             (gen/tuple
                               (gen/return (inc v))
                               (gen/choose 0 v))))]
      (> x y))
    
Michał Marczyk
  • 83,634
  • 13
  • 201
  • 212
  • #1 doesn't work for me because instead of `x`, in my actual use, it's a list. And `y` needs to be less than the length of the list. It's clever, though. – Dan Burton Nov 19 '15 at 02:17
  • Regarding #2, wouldn't generating them separately cause the `prop/forall` to forget some of the information and not display everything nicely? – Dan Burton Nov 19 '15 at 02:21
  • Re: #1, that changes the problem, and in that case I'd consider using `bind`. I've added an approach based on `bind` and `choose` that seems fairly clean to me (for your actual use case, I imagine you'll want to generate `x` first, compute its length and use that as the second argument to `choose`). – Michał Marczyk Nov 19 '15 at 03:55
  • Re: #2, `such-that` may not succeed in finding a satisfactory value at all, in which case it'll produce an error (unlikely in this case given the probabilities involved; it does accept an optional `max-tries` argument in case you'd like it to try harder), but if it succeeds, the result should be as if you never used `such-that`, but the wrapped generator just happened to produce only the kinds of values that your predicate accepts. In other words, the only information lost is that on the failed attempts to generate an acceptable value. – Michał Marczyk Nov 19 '15 at 03:56
3

You can use the bind function to create a generator that depends on a previous value. I'm sure there has to be a cleaner way to do this.

(def always-greater-than
  (gen/bind gen/nat
           (fn [v] (gen/tuple (gen/return (inc v))
                              (gen/fmap #(mod % (inc v)) gen/nat)))))

Example output:

(gen/sample always-greater-than)
([1 0] [2 0] [1 0] [3 0] [1 0] [6 4] [7 6] [2 0] [3 2] [6 4])
Mike
  • 3,356
  • 3
  • 29
  • 27
  • Thank you, I was thinking about solving it with this sort of technique, and this helps me see how to actually accomplish it in this way. – Dan Burton Nov 19 '15 at 02:18