ifigueroap / racket-quickcheck

Quickcheck Clone implemented in Racket
Other
31 stars 15 forks source link

Nested property definitions #27

Open ifigueroap opened 8 years ago

ifigueroap commented 8 years ago

I have a simple macro that allows one to define the following:

(define arbitrary-int-char-list-same-length
    (property* ([n arbitrary-natural]
                [int-list (choose-list (arbitrary-generator arbitrary-integer) n)]
                [char-list (choose-list (arbitrary-generator arbitrary-char) n)])
              (display n)(newline)
              (and (equal? (length int-list) (length char-list)) (equal? (length char-list) n))))

which for instance can be used to test a zipWith-like function, where list must have the same length. The macro is:

(define-syntax make-property*
  (syntax-rules ()
    [(make-property* () ?body0 ?body1 ...)
     (make-property () ?body0 ?body1 ...)]    

    [(make-property* ((?id1 ?gen1) (?id ?gen) ...) ?body0 ?body1 ...)
     (make-property ((?id1 ?gen1))
                   (make-property* ((?id ?gen) ...) ?body0 ?body1 ...))]))

and must go into 'private/property.rkt'. I want to push it to master and then backport it to tag v0.1. For now I don't have time :(

jackfirth commented 8 years ago

If arbitraries supported a map operation on them, you could do that this way as well:

(define zip-with-preserves-length
  (property ([lists (arbitrary-lists-of-same-length arbitrary-integer arbitrary-char)])
    (define left-list (first lists))
    (define right-list (second lists))
    (define zipped (zip left-list right-list))
    (and (equal? (length left-list) (length right-list) (length zipped-llist))))

(define (arbitrary-lists-of-same-length . elem-arbs)
  (define arb-items (apply arbitrary-tuple elem-arbs))
  (define arb-tuplelist (arbitrary-list arb-items))
  (define (nth-item-arbitrary n)
    (map-arbitrary (lambda (vs) (list-ref vs n)) arb-tuplelist))
  (map nth-item-arbitrary (range (length elem-arbs))))

Where (map-arbitrary f arb) produces a new arbitrary that produces the results of calling f on the output items of arb.

I'm working on an alternative implementation of arbitraries that allows:

1) More generic narrowing of the search space of items than purely through size bounds 2) Shrinking 3) Ease of producing "interesting" results in compound arbitraries 4) Ease of extension through map and filter

It's a ways off from done however. We should add this macro now and make a v0.2 tag that includes the doc changes from #25.

ifigueroap commented 8 years ago

I agree in principle with doing things better :) But I also like to have a simple interface, similar to let* for these kind of definitions. Notice that in the first approach you can still directly use the arbitrary n, whereas in the map-based approach it is a property computed from the list themselves.

I've been thinking a bit about arbitrary/generator composition and we should really exploit their monadic nature. However, instead of classical do notation from Haskell we could try something similar to Scala for-comprehensions for futures... what do you think?

jackfirth commented 8 years ago

I'm not sure a monadic API for properties makes as much sense in Racket as it does in Haskell. It would be useful for composing arbitraries however, for instance a function with type Arbitrary a -> (a -> Arbitrary b) -> Arbitrary b would be useful, as it would make implementing things like "an arbitrary matrix" much much easier.