This section illustrates the current state of Racket’s contract implementation with a series of examples from Design by Contract, by Example [Mitchell02].
Mitchell and McKim’s principles for design by contract DbC are derived from the 1970s style algebraic specifications. The overall goal of DbC is to specify the constructors of an algebra in terms of its observers. While we reformulate Mitchell and McKim’s terminology and we use a mostly applicative approach, we retain their terminology of “classes” and “objects”:
Separate queries from commands.
A query returns a result but does not change the observable properties of an object. A command changes the visible properties of an object, but does not return a result. In applicative implementation a command typically returns an new object of the same class.
Separate basic queries from derived queries.
A derived query returns a result that is computable in terms of basic queries.
For each derived query, write a post-condition contract that specifies the result in terms of the basic queries.
For each command, write a post-condition contract that specifies the changes to the observable properties in terms of the basic queries.
For each query and command, decide on a suitable pre-condition contract.
Each of the following sections corresponds to a chapter in Mitchell and McKim’s book (but not all chapters show up here). We recommend that you read the contracts first (near the end of the first modules), then the implementation (in the first modules), and then the test module (at the end of each section).
Mitchell and McKim use Eiffel as the underlying programming language and employ a conventional imperative programming style. Our long-term goal is to transliterate their examples into applicative Racket, structure-oriented imperative Racket, and Racket’s class system.
Note: To mimic Mitchell and McKim’s informal notion of parametericity (parametric polymorphism), we use first-class contracts. At several places, this use of first-class contracts improves on Mitchell and McKim’s design (see comments in interfaces).
This first module contains some struct definitions in a separate module in order to better track bugs.
;data definitions;interface;end of interface
This module contains the program that uses the above.
;implementation;[listof (list basic-customer? secret-info)];end of implementation;how many customers are in the db?;is the customer with this id active?;what is the name of the customer with this id?;change the name of the customer with this id;A pre-post condition contract must use;a side-effect to express this contract;via post-conditions
The tests:
(add(make-basic-customer'mf"matthias""brookstone"))(add(make-basic-customer'rf"robby""beverly hills park"))(add(make-basic-customer'fl"matthew""pepper clouds town"))(add(make-basic-customer'sk"shriram""i city"))"manager"
;a contract utility(stack-p?s)(stack-eqs)));predicate;primitive queries;how many items are on the stack?;which item is at the given position?[item-at()[result(stack-p?s)])];derived queries;is the stack empty?[is-empty?();which item is at the top of the stack[top()[t(stack-p?s)];a stack item, t is its name#:post-cond;creation[initialize();Mitchell and McKim use (= (count s) 0) here to express;the post-condition in terms of a primitive query;commands;add an item to the top of the stack[push()[snstack?];result kind#:post-cond([stack-eqs]x(topsn))))];remove the item at the top of the stack[pop()[snstack?];result kind#:post-cond
The tests:
"stack""empty""push exn"#f))
;a shorthand for use below;implementation;the keys should probably be another parameter (exercise)(dictionary-value?d)(dictionary-eq?d)))(make-dictionary(cond(dictionary-value?d)(dictionary-eq?d)));end of implementation;interface;predicates;basic queries;how many items are in the dictionary?;does the dictionary define key k?()#:post-cond;what is the value of key k in this dictionary?()[result(dictionary-value?d)])];initialization;post condition: for all k in symbol, (has? d k) is false.();commands;Mitchell and McKim say that put shouldn't consume Void (null ptr);for v. We allow the client to specify a contract for all values;via initialize. We could do the same via a key? parameter;(exercise). add key k with value v to this dictionary[v(dictionary-value?d)])()[resultdictionary?]#:post-cond([dictionary-eq?d](value-forresultk)v)))];remove key k from this dictionary()#:post-cond;end of interface
The tests:
"dictionaries"
;Note: this queue doesn't implement the capacity restriction;of Mitchell and McKim's queue but this is easy to add.;a contract utility;implementation(queue-p?q)(queue-eqq)))(queue-p?s)(queue-eqs)));interface;predicate;primitive queries;Imagine providing this 'query' for the interface of the module;only. Then in Racket there is no reason to have count or is-empty?;around (other than providing it to clients). After all items is;exactly as cheap as count.;derived queries;We could express this second part of the post;condition even if count were a module "attribute";in the language of Eiffel; indeed it would use the;exact same syntax (minus the arrow and domain).()()();creation;commands()[resultqueue?()[result;end of interface
The tests:
"queue""empty"#f))