Contract notes ============== Contracts are a way of making sure we call functions with the right kinds of values, and also help check that the return values of our functions are also correct. Since Scheme is not a statically typed language, it's easy for us to make silly domain/range errors. The contract system is meant to balance that problem so that when we do make mistakes, we'll at least get good error messages. Like many things in mzscheme, mzscheme's contract system is part of the 'mzlib' standard library, so if we want to use them in our own programs, we need to first use: (require (lib "contract.ss")) to pull contract support into mzscheme. Other docs to look at --------------------- The 'Design by Contract' entry in the Wikipedia gives a good conceptual overview of why contracts are useful: http://en.wikipedia.org/wiki/Design_by_contract The reference documentation on contracts lives in: http://download.plt-scheme.org/doc/299.400/html/mzlib/mzlib-Z-H-13.html#node_chap_13 There's also a nice introduction to the contract system here in the paper "Contracts for Higher-Order Functions". http://people.cs.uchicago.edu/~robby/publications/papers/ho-contracts-icfp2002.pdf The Blame Game -------------- Let's start by going through a simple, quick-and-dirty example. And what better way to start simple than to reject complex numbers? Let's pretend that we don't believe in complex numbers, and would like to write a square root function that complains when given negative values. We already know to do something like this, by using error: > (module mysqrt-module mzscheme (provide mysqrt) (define (mysqrt x) (if (< x 0) (error 'mysqrt "I don't believe in complex numbers") (sqrt x)))) > (require mysqrt-module) > (mysqrt 3) 1.7320508075688772 > (mysqrt -1) mysqrt: I don't believe in complex numbers One issue, though, is that our error-handling code becomes a part of the function body, and that's ugly. Contracts let us attach those value checks without pervading the function body: > (module mysqrt-module mzscheme (require (lib "contract.ss")) (provide/contract (mysqrt (-> (>=/c 0) (>=/c 0)))) (define (mysqrt x) (sqrt x))) > > (mysqrt 3) 1.7320508075688772 > (mysqrt -1) repl-15:1:1: top-level broke the contract (-> (>=/c 0) (>=/c 0)) it had with mysqrt-module on mysqrt; expected <(>=/c 0)>, given: -1 The function still dies predictably, and also tells us exactly why it errored out. The main advantage of the contract system is that, if used right, errors point directly at the culprit of the domain-range problem. We see that the contract blames the top-level for daring to call mysqrt with bad input, but we can just as easily use this to see if a function violates the contract with bad output: > (module mysqrt-module mzscheme (require (lib "contract.ss")) (provide/contract (mysqrt (-> (>=/c 0) (>=/c 0)))) (define (mysqrt x) 'oops)) > > (require mysqrt-module) > (mysqrt 3) repl-5:1:1: mysqrt-module broke the contract (-> (>=/c 0) (>=/c 0)) it had with top-level on mysqrt; expected <(>=/c 0)>, given: oops So a contract is an agreement upheld by both the caller and callee. When bad things happen, the contract system allows us to blame the responsible party. A second look at the first example ---------------------------------- Going back to our original example, let's look more closely at the use of provide/contract to attach contracts to functions: (provide/contract (mysqrt (-> (>=/c 0) (>=/c 0)))) We're telling the system that mysqrt should be provided to the outside world, and with a particular "contract" that takes nonnegative numbers to nonnegative numbers. Contracts are things that we can hold. There are simple "flat" value contracts: > (require (lib "contract.ss")) > false/c # as well as contracts on functions, as we saw above with the arrow notation: > (-> (>=/c 0) (>=/c 0)) # To make the function contract here a little easier to read, we can take advantage of mzscheme's support of infix syntax: > ((>=/c 0) . -> . (>=/c 0)) # which can scan better for people. Another thing we can do to improve the readability of error messages is to give a concrete name to the part that checks for nonnegative numbers: > (define nonnegative/c (flat-named-contract 'nonnegative/c (lambda (n) (>= n 0)))) Let's combine these altogether and see what things look like: > (module mysqrt-module mzscheme (require (lib "contract.ss")) (define nonnegative/c (flat-named-contract 'nonnegative/c (lambda (n) (>= n 0)))) (provide/contract (mysqrt (nonnegative/c . -> . nonnegative/c))) (define (mysqrt x) (sqrt x))) > > (require mysqrt-module) > (mysqrt 3) 1.7320508075688772 > (mysqrt 0) 0 > (mysqrt -1) repl-33:1:1: top-level broke the contract (-> nonnegative/c nonnegative/c) it had with mysqrt-module on mysqrt; expected , given: -1 A Matrix -------- Although contracts could be considered a kind of run-time type checking, because the checking is done at run-time, not only can we check for types violations, but we can also do more interesting checks on dynamic domain/range conditions. A Matrix Revisited ------------------