You are looking at historical revision 24372 of this page. It may differ significantly from its current revision.
- Version History
Design by Contract
"Design by contract" is a metaphor coined by Bertrand Meyer for his purely object oriented language Eiffel. The idea behind it is to separate the concerns of suppliers and clients of software modules: The client of a routine is responsible for calling it with correct arguments. The supplier can rely on it, she/he mustn't check it again. The supplier in turn is responsible for delivering correct results, provided the routine's arguments are valid. If not, the supplier needn't do any work at all.
In this metaphor a module is something like a contract between the supplier and the client of that module. But like a contract in social life, it's useless if not properly documented. Hence the "small print" of our module should be documented automatically, so that each party knows about each others duties ....
Another metaphor of Meyer's is "command-query-separation", where in Eiffel a command is working by side effect (it changes the object's state) and a query is a pure function (it reports the object's state without changing it). His advice is, never to do both in one routine, write two instead.
Implementation and use
This module is an attempt to bring Design by Contract to Chicken Scheme. In effect, it replaces define and define-syntax by new macros define-with-contract and define-syntax-with-contract respectively, where - in the long form - the lambda or syntax-rules expression is preceeded by a contract expression. A short form is available as well, where the call pattern of the procedure is followed by the contract clauses and the procedure's body.
To achieve automatic documentation, these two macros have to be wrapped by a call of the parameter
initializing documentation and the definition
(define module-name (doclist->dispatcher (doclist)))
saving it in a dispatcher routine.
The case of procedures
For procedures a contract expression starts with the symbol contract and contains a list of clauses, where each clause is either
- the pattern of a typical procedure's call, the only required clause,
- a documentation string,
- a list starting with the keyword domain: and containing checks of the assumptions,
- a list starting with the keyword results: containing variable names which are bound to the results of the procedure; if this list is not supplied (results: result) is assumed. This is now obsolete and should be replaced by (with-results (res0 res1 ...) . body) within the range: clause below.
- a list starting with the keyword range: followed either by (with-results (result0 result1 ...) xpr0 xpr1 ...) or by xpr0 xpr1 ..., where xpr0 xpr1 are predicates on result0 result1 ... or the default variable name result.
- a list starting with the keyword effect: which contains triples of the form (state query change [equ?]) where state is bound to the query expression before the command call and the change expression is compared with equal? [or equ?, if supplied] to another call of query after the command call.
Note, that command-query-separation demands, that only one of a range: and an effect: clause are allowed.
The case of macros
For syntax-rules macros (and the [ir-]macro-rules or er-macro-rules macros of the (ir-|er-)macros eggs) the contract expression is simply a docstring. After all, those macro-transformers have domain checks already built-in in form of the pattern matching process, it needs only be automatically documented.
For raw low-level macros based on (er-|ir-)macro-transformer, it's a list starting with the keyword forms: which contains the admissible patterns of the macro's call and a mandatory documentation string.
contract[syntax] (contract (name . args) clause ...)
where each clause is one of
- a documentation string
- (domain: assumption ...)
- (results: result0 result1 ...)
If this clause is not supplied (results: result) is assumed. This clause is deprecated.
- (range: proposition ...) or (range: (with-results (res0 res1 ...) proposition ...)
- (effect: (state query change [equ?]) ...)
One of[syntax] (define-with-contract name (contract (name . args) clause ...) (lambda args . body))
[syntax] (define-with-contract name (let ((var val) ...) (contract (name . args) clause ...) (lambda args . body)))
[syntax] (define-with-contract (name . args) clause ... . body)
where the admissible clauses are described above and instead of let another binding construct can be used as well.
One of[syntax] (define-syntax-with-contract name docstring rules)
[syntax] (define-syntax-with-contract name (forms: form0 form1 ... docstring) transformer)
where docstring is a documentation string, rules is either a syntax-rules expression or one of er-macro-rules, [ir-]macro-rules from the (ir-|er-)macros package, each form is the pattern of an admissible call of name and transformer is a raw low-level macro-transformer.
doclist[parameter] (doclist '())
should be called before the first define[-syntax]-with-contract expression to initialize automatic documentation.
doclist->dispatcher[procedure] (doclist->dispatcher (doclist))
saves (doclist) in a dispatcher. A typical use is
(define module-name (doclist->dispatcher (doclist)))
which should be called after the last define[-syntax]-with-contract expression to save the automatic documentation in module-name. This procedure can than be called by the module's client with or without a symbol argument.
Without argument the call returns the list of exported symbols, with argument the call returns the textual representaion of the contract of the module's exported symbol sym.
prints the documentation of the whole module in readable form.
contracts[procedure] (contracts [sym])
prints the contract of the exported symbol sym of the contracts module or the list of exported symbols when called as a thunk.
see the Design by Contracts tutorial in /design-by-contract
data-structures, extras, srfi-13
Copyright (c) 2011, Juergen Lorenz All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- (results: ...) made obsolete, use (with-results (name ...) . body) within (range: ...) instead
- changed (effect: ...), removed (state: ...) (invariant: ...)
- some enhancements
- added print-doclist, fixed typo in setup script reported by mario
- initial import