[racket] Introductory talk on Contracts and Functional Contracts, with most examples in Racket

From: Matthias Felleisen (matthias at ccs.neu.edu)
Date: Tue Sep 3 17:58:34 EDT 2013

On Sep 3, 2013, at 5:51 PM, Daniel Prager <daniel.a.prager at gmail.com> wrote:

> Hi Matthias
> 
> Thank-you for your sage advice:
> 
> On Tue, Sep 3, 2013 at 11:54 PM, Matthias Felleisen <matthias at ccs.neu.edu> wrote:
> 
> 1. Always use contracts that cannot be expressed as types
> in an obvious type system.
> 
> I'll work on my examples.  I may also throw in an example in typed racket to show the types and contracts working together.


You can't. Sam has had this combination on his todo list for 7 years and he never got around to it. Perhaps I signed his dissertation too early :-)



> 2. Show a higher-order contract and flavors that push this idea hard. Consider using d/dx or indefinite adaptive integral ...

> I'm a little bit wary of showing (only) a calculus-flavoured example because I don't want to give the impression that  the higher-order contracts are just for mathematical applications.


Good. So use this: 

 (contract-out 
  ;; Nat Nat [Nat] [Nat] [[Listof X]] [[Listof Y]] ->* Image [Nat Nat ->* X Y]
  ;; (grid n m x-delta y-delta x-labels y-labels) creates:
  ;; -- a graphical grid of size (* n x-delta) x (* m y-delta) 
  ;;    divvied up into n x m tiles of size x-delta x y-delta 
  ;;    labeled with x-labels x y-labels 
  ;; -- a function event-handler that deals with mouse clicks for this grid 
  ;; (event-handler x y) = (an-x-lablel, a-y-label)
  ;; iff (x,y) is a mouse click in the tile labeled (an-x-lablel, a-y-label) 
  (grid (->i ((n natural-number/c) (m natural-number/c))
             ((delta-x natural-number/c) 
              (delta-y natural-number/c) 
              (x-labels list?)
              (y-labels list?)
              #:grid? (grid? #t))
             #:pre/name (n x-labels) "correct # of x labels"
             (or (unsupplied-arg? x-labels) (= (length x-labels) n))
             #:pre/name (m y-labels) "correct # of y labels"
             (or (unsupplied-arg? y-labels) (= (length y-labels) m))
             (values
              (grid-scene image?)
              (event-handler 
               (delta-x delta-y n m x-labels y-labels)
               (->i ((x natural-number/c) (y natural-number/c))
                    #:pre/name (x) "x in proper range"
                    (or (unsupplied-arg? delta-x) (< x (* n delta-x)))
                    #:pre/name (y) "y in proper range"
                    (or (unsupplied-arg? delta-y) (< y (* m delta-y)))
                    (values (xl (if (unsupplied-arg? x-labels) any/c (apply or/c x-labels)))
                            (yl (if (unsupplied-arg? y-labels) any/c (apply or/c y-labels)))))))))

This is from my code base. It is a function that consumes the dimensions of a (functional image) canvas and created a gridded image and an event handler that tells you on which "tile" a user clicked. It is highly real, higher-order, dependent, and yet not difficult to understand. 

Full code below. 


> I confess that as I have been learning about higher-order contracts I wondered whether (instead) types + flat-contracts would suffice in practice as a worse-is-better compromise.

That's what Meyer thought with Eiffel. It assumes a closed world of software project with a central registry of higher-order types combined with contracts to avoid rampant code duplication. Some people want to live in this world; real people can't. 


> 3. For the smack-down, contracts tend to reveal some things that are "intensional" (part of the implementation).
> 
> Sounds bad for information hiding.  What's an example of this?

The error estimate for adaptive integration is a function of the delta you impose on one "iteration" of the algorithm. 

-- Matthias


;; ---------------------------------------------------------------------------------------------------
(provide
 (contract-out 
  ;; Nat Nat [Nat] [Nat] [[Listof X]] [[Listof Y]] ->* Image [Nat Nat ->* X Y]
  ;; (grid n m x-delta y-delta x-labels y-labels) creates:
  ;; -- a graphical grid of size (* n x-delta) x (* m y-delta) 
  ;;    divvied up into n x m tiles of size x-delta x y-delta 
  ;;    labeled with x-labels x y-labels 
  ;; -- a function event-handler that deals with mouse clicks for this grid 
  ;; (event-handler x y) = (an-x-lablel, a-y-label)
  ;; iff (x,y) is a mouse click in the tile labeled (an-x-lablel, a-y-label) 
  (grid (->i ((n natural-number/c) (m natural-number/c))
             ((delta-x natural-number/c) 
              (delta-y natural-number/c) 
              (x-labels list?)
              (y-labels list?)
              #:grid? (grid? #t))
             #:pre/name (n x-labels) "correct # of x labels"
             (or (unsupplied-arg? x-labels) (= (length x-labels) n))
             #:pre/name (m y-labels) "correct # of y labels"
             (or (unsupplied-arg? y-labels) (= (length y-labels) m))
             (values
              (grid-scene image?)
              (event-handler 
               (delta-x delta-y n m x-labels y-labels)
               (->i ((x natural-number/c) (y natural-number/c))
                    #:pre/name (x) "x in proper range"
                    (or (unsupplied-arg? delta-x) (< x (* n delta-x)))
                    #:pre/name (y) "y in proper range"
                    (or (unsupplied-arg? delta-y) (< y (* m delta-y)))
                    (values (xl (if (unsupplied-arg? x-labels) any/c (apply or/c x-labels)))
                            (yl (if (unsupplied-arg? y-labels) any/c (apply or/c y-labels)))))))))))

;; ---------------------------------------------------------------------------------------------------
;; implementation section 

(require 2htdp/image)
(module+ test 
  (require rackunit))

(define/contract 
  (grid n m (delta-x 20) (delta-y 20) (x-labels (build-list n add1)) (y-labels (build-list m add1))
        #:grid? (grid? #t))
  #;
  (->i ((n natural-number/c) (m natural-number/c))
       ((delta-x natural-number/c) 
        (delta-y natural-number/c) 
        (x-labels list?)
        (y-labels list?)
        #:grid? (grid? #t))
       #:pre/name (n x-labels) "correct # of x labels"
       (or (unsupplied-arg? x-labels) (= (length x-labels) n))
       #:pre/name (m y-labels) "correct # of y labels"
       (or (unsupplied-arg? y-labels) (= (length y-labels) m))
       (values
        (grid-scene image?)
        (event-handler 
         (delta-x delta-y n m x-labels y-labels)
         (->i ((x natural-number/c) (y natural-number/c))
              #:pre/name (x) "x in proper range"
              (or (unsupplied-arg? delta-x) (< x (* n delta-x)))
              #:pre/name (y) "y in proper range"
              (or (unsupplied-arg? delta-y) (< y (* m delta-y)))
              (values (xl (if (unsupplied-arg? x-labels) any/c (apply or/c x-labels)))
                      (yl (if (unsupplied-arg? y-labels) any/c (apply or/c y-labels))))))))
  any/c
  (values (image-grid n m delta-x delta-y grid?)
          (lambda (x y) 
            (values (search x x-labels delta-x) (search y y-labels delta-y)))))

;; Nat Nat Nat Nat Boolean -> Image 
(define (image-grid n m delta-x delta-y grid?)
  (define tile (rectangle delta-x delta-y 'outline (if grid? 'black 'white)))
  (define arow (apply beside (make-list n tile)))
  (define col* (apply above  (make-list m arow)))
  col*)

;; Nat [Listof X] Nat -> X 
(module+ test
  (check-equal? (search 95 (build-list 20 add1) 10) 10))
(define/contract (search z labels delta)
  (->i ((z natural-number/c) (labels (listof any/c)) (delta natural-number/c))
       #:pre (z labels delta) (< z (* (length labels) delta))
       any)
  (cond
    [(< z delta) (first labels)]
    [else (search (- z delta) (rest labels) delta)]))

(module+ test 
  (define n 20)
  (define m 20)
  (define x-labels (build-list n add1))
  (define y-labels (build-list m add1))
  (check-equal? (let*-values (((_ g) (grid n m 10 10 x-labels y-labels))
                              ((x y) (g 99 99)))
                  (cons x y))
                (cons 10 10))
  (check-equal? (let*-values (((_ g) (grid 20 20 10 10)) 
                              ((x y) (g 99 99)))
                  (cons x y))
                (cons 10 10))
  (check-equal? (let*-values (((_ g) (grid 20 20)) 
                              ((x y) (g 99 99)))
                  (cons x y))
                (cons 5 5)))

;; ---------------------------------------------------------------------------------------------------
;; for exploration only
(require 2htdp/universe)
(define (play)
  (define-values (background keyhandler) (grid 20 20 50 50 #:grid? #f))
  (define (add-dot p i)
    (match p
      [`(,x ,a ,y ,b)
       (define label (text (format "(~a,~a)" a b) 22 'black))
       (place-image/align label x y "center" "center" i)]))
  (big-bang '()
            (to-draw (λ (w) (foldr add-dot background w)))
            (on-mouse (λ (w x y me)
                        (cond
                          [(mouse=? "button-down" me)
                           (define-values (a b) (keyhandler x y))
                           (cons (list x a y b) w)]
                          [else w])))))





Posted on the users mailing list.