source: project/wiki/eggref/4/contracts @ 33175

Last change on this file since 33175 was 33175, checked in by svnwiki, 4 years ago

Anonymous wiki edit for IP [76.103.144.73]: Typo in import

File size: 15.7 KB
Line 
1[[tags: egg]]
2[[toc:]]
3
4== contracts
5
6This egg is now obsolete. Use dbc instead
7
8=== Design by Contract
9
10"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.
11
12In 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 ....
13
14=== Command-query-separation
15
16Another 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.
17
18=== Implementation and use
19
20This 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.
21
22To achieve automatic documentation, these two macros have to be wrapped by a call of the parameter
23
24<enscript highlight=scheme>(doclist '())</enscript>
25
26initializing documentation and the definition
27
28<enscript highlight=scheme>(define module-name (doclist->dispatcher (doclist)))</enscript>
29
30saving it in a dispatcher routine.
31
32==== The case of procedures
33
34For procedures a contract expression starts with the symbol contract and contains a list of clauses, where each clause is either
35
36* the pattern of a typical procedure's call, the only required clause,
37
38* a documentation string,
39
40* a list starting with the keyword domain: (or the literal domain) and containing checks of the assumptions,
41
42* a list starting with the keyword range: (or the literal 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.
43
44* a list starting with the keyword effect: (or the literal 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.
45
46Note, that command-query-separation demands, that only one of a range: and an effect: clause are allowed.
47
48==== The case of macros
49
50For syntax-rules macros as well as ir-macro-rules and er-macro-rules macros 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.
51
52For raw low-level macros based on (er-|ir-)macro-transformer, it's a list starting with the macro code (name . rest) which will be matched against the macro's use and an optional documentation string.
53
54=== Programming interface of module contract-helpers
55
56==== contract-helpers
57
58<procedure>(contract-helpers [sym])</procedure>
59
60prints the contract of the exported symbol sym of the contract-helpers module or the list of exported symbols when called as a thunk.
61
62==== er-macro-rules
63
64<syntax>(er-macro-rules (%sym ...) (code0 xpr0) (code1 xpr1) ...)</syntax>
65
66references a renamed version of sym ... under the name %sym ... and pairs the differnt macro-codes code0 code1 ... with expressions xpr0 xpr1 ..., which usually evalute to backquoted templates.
67
68This macro is unhygienic by design, it introduces the symbol compare? into its scope.
69
70==== ir-macro-rules
71
72<syntax>(ir-macro-rules (sym ...) (code0 xpr0) (code1 xpr1) ...)</syntax>
73
74pairs the differnt macro-codes code0 code1 ... with expressions xpr0 xpr1 ..., which usually evalute to backquoted templates in the scope of injected symbols sym ....
75
76This macro is unhygienic by design, it introduces the two symbols inject and compare? into its scope.
77
78==== bind
79
80<syntax>(bind pat xpr . body)</syntax>
81
82binds the pattern variables of the nested lambda-list pat to corresponding subexpressions of the nested pseudolist xpr and executes body in this context.
83
84==== bind-case
85
86<syntax>(bind-case xpr (pat0 . body0) (pat1 . body1) ...)</syntax>
87
88matches nested pseudolist-expression xpr against patterns pat0 pat1 ...  in sequence, binding the variables of the first matching pattern to corresponding subexpressions of xpr and executes body of the first matching pattern in this context.
89
90==== doclist
91
92<parameter>(doclist '())</parameter>
93
94should be called before the first define[-syntax]-with-contract expression to initialize automatic documentation.
95
96==== doclist->dispatcher
97
98<procedure>(doclist->dispatcher (doclist))</procedure>
99
100saves (doclist) in a dispatcher. A typical use is
101
102<enscript highlight=scheme>
103(define module-name (doclist->dispatcher (doclist)))
104</enscript>
105
106which 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.
107
108<enscript highlight=scheme>
109(module-name [sym])
110</enscript>
111
112Without 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.
113
114==== print-doclist
115
116<procedure>(print-doclist)</procedure>
117
118prints the documentation of the whole module in readable form.
119
120=== Programming interface of module contracts
121
122All exported symbols of contract-helpers are passed through, so that it's only necessary to import contracts.
123
124==== contracts
125
126<procedure>(contracts [sym])</procedure>
127
128prints the contract of the exported symbol sym of the contracts module or the list of exported symbols when called as a thunk.
129
130==== contract
131
132<syntax>(contract (name . args) clause ...)</syntax>
133
134where each clause is one of
135
136* a documentation string
137
138* {{(domain: assumption ...)}}
139
140* {{(range: proposition ...) or (range: (with-results (res0 res1 ...) proposition ...)}}
141
142* {{(effect: (state query change [equ?]) ...)}}
143
144==== define-with-contract
145
146One of
147
148<syntax>(define-with-contract name (contract (name . args) clause ...) (lambda args . body))</syntax>
149
150<syntax>(define-with-contract name (let ((var val) ...) (contract (name . args) clause ...) (lambda args . body)))</syntax>
151
152<syntax>(define-with-contract (name . args) clause ... . body)</syntax>
153
154where the admissible clauses are described above and instead of let another binding construct can be used as well.
155
156==== define-syntax-with-contract
157
158One of
159
160<syntax>(define-syntax-with-contract name docstring rules)</syntax>
161
162where rules is one of
163
164* {{(syntax-rules (sym ...) (pat0 tpl0) (pat1 tpl1) ...)}}
165
166* {{(ir-macro-rules (sym ...) (pat0 xpr0) (pat1 xpr1) ...)}}
167
168* {{(er-macro-rules (%sym ...) (pat0 xpr0) (pat1 xpr1) ...)}}
169
170and docstring is optional,
171
172<syntax>(define-syntax-with-contract name (syntax-contract (name . rest) docstring) transformer)</syntax>
173
174where docstring is optional and transformer is a raw low-level macro-transformer,
175
176<syntax>(define-syntax-with-contract (name . rest) docstring with-expression)</syntax>
177
178where docstring is optional and with-expression is one of
179
180* {{(literal syms . body)}}
181
182* {{(with-renamed syms . body)}}
183
184* {{(with-injected syms . body)}}
185
186which will be translated to syntax-rules, er-macro-rules or ir-macro-rules respectively.
187
188==== define-macro-with-contract
189
190<syntax>(define-macro-with-contract code docstring body))</syntax>
191
192where docstring is optional, code is the complete macro-code (name . args), i.e. the pattern of a macro call, and body is one of
193
194  (with-renamed (%sym ...) xpr . xprs)
195  (with-injected (sym ...) xpr . xprs)
196  xpr . xprs
197
198In the first case each %sym is a renamed version of sym, in the second sym is injected as is, i.e. not renamed, and in the last case no symbol is injected, i.e. the macro is hygienic.
199
200== Usage
201
202<enscript highlight=scheme>
203(use contracts)
204(import-for-syntax
205  (only contacts er-macro-rules ir-macro-rules))
206</enscript>
207
208== Examples
209
210<enscript highlight=scheme>
211
212(use contracts)
213
214(import-for-syntax
215 (only contracts ir-macro-rules er-macro-rules))
216
217;;; initialize documentation
218(doclist '())
219
220;;; a single datatype as an alternative to boxes
221
222;; predicate
223(define-with-contract (single? xpr)
224   "check, if xpr evaluates to a single"
225   (and (procedure? xpr)
226        (condition-case (eq? 'single (xpr (lambda (a b c) a)))
227          ((exn) #f))))
228
229;; constructor
230(define-with-contract (single xpr)
231  "package the value of xpr into a single object"
232  (domain: (true? xpr))
233  (range: (single? result))
234  (lambda (sel)
235    (sel 'single xpr (lambda (new) (set! xpr new)))))
236
237;; query
238(define-with-contract (single-state sg)
239  "returns the state of the single object sg"
240  (domain: (single? sg))
241  (range: (true? result))
242  (sg (lambda (a b c) b)))
243
244;; command
245(define-with-contract (single-state! sg arg)
246  "replaces state of sg with arg"
247  (domain: (single? sg) (true? arg))
248  (effect: (state (single-state sg) arg))
249  ((sg (lambda (a b c) c)) arg))
250
251;;; Euclid's integer division as an example for a
252;;; function with two results
253
254(define-with-contract (quotient+remainder m n)
255  "integer division"
256  (domain:
257    (integer? m)
258    (not (negative? m))
259    (integer? n)
260    (positive? n)
261    (<= n m))
262  (range:
263    (with-results (q r)
264      (integer? q)
265      (integer? r)
266      (= (+ (* q n) r) m)))
267  (let loop ((q 0) (r m))
268    (if (< r n)
269      (values  q r)
270      (loop (add1 q) (- r n)))))
271 
272;;; the same trivial freeze macro implemented in different styles
273
274(define-syntax-with-contract (sefreeze xpr)
275  "sefreeze"
276  (with-renamed (%lambda) `(,%lambda () ,xpr)))
277
278(define-syntax-with-contract (sifreeze xpr)
279  "sifreeze"
280  (with-injected () `(lambda () ,xpr)))
281
282(define-syntax-with-contract (ssfreeze xpr)
283  "ssfreeze"
284  (literal () (lambda () xpr)))
285
286(define-syntax-with-contract sfreeze
287  "sfreeze"
288  (syntax-rules ()
289    ((_ xpr) (lambda () xpr))))
290
291(define-syntax-with-contract ifreeze
292  "ifreeze"
293  (ir-macro-rules ()
294    ((_ xpr) `(lambda () ,xpr))))
295
296(define-syntax-with-contract efreeze
297  "efreeze"
298  (er-macro-rules (%lambda)
299    ((_ xpr) `(,%lambda () ,xpr))))
300
301(define-syntax-with-contract lifreeze
302  (syntax-contract (lifreeze xpr) "lifreeze")
303  (ir-macro-transformer
304    (lambda (f i c) `(lambda () ,(cadr f)))))
305
306(define-syntax-with-contract lefreeze
307  (syntax-contract (lefreeze xpr) "lefreeze")
308  (er-macro-transformer
309    (lambda (f r c) `(,(r 'lambda) () ,(cadr f)))))
310
311(define-syntax-with-contract lfreeze
312  (syntax-contract (lfreeze xpr) "lfreeze")
313  (lambda (f r c) `(,(r 'lambda) () ,(cadr f))))
314
315;;; explicit- and implicit-renaming versions of or
316
317(define-syntax-with-contract er-or
318  "er-version of or"
319  (er-macro-rules (%if %er-or)
320    ((_) #f)
321    ((_ arg . args)
322     `(,%if ,arg ,arg (,%er-or ,@args)))))
323 
324(define-syntax-with-contract ir-or
325  "ir-version of or"
326  (ir-macro-rules ()
327    ((_) #f)
328    ((_ arg . args)
329     `(if ,arg ,arg (ir-or ,@args)))))
330 
331(define-macro-with-contract (our-or . args)
332  "a private version of or"
333  (if (null? args)
334    #f
335    (let ((tmp (car args)))
336      `(if ,tmp ,tmp (our-or ,@(cdr args))))))
337
338(define-macro-with-contract (my-or . args)
339  "a variant of or"
340  (with-renamed (%if %my-or)
341    (if (null? args)
342      #f
343      (let ((tmp (car args)))
344        `(,%if ,tmp ,tmp (,%my-or ,@(cdr args)))))))
345
346;; save documantation in dispatcher
347(define docs (doclist->dispatcher (doclist)))
348
349;; some binding-examples with results
350
351(bind x 1 x)  ; -> 1
352
353(bind (x (y (z . u) v) . w) '(1 (2 (3) 4) 5 6)
354        (list x y z u v w)) ; -> (1 2 3 () 4 (5 6))
355
356(bind (x (y (z . u) v) . w) '(1 (2 (3 . 3) 4) 5 6)
357        (list x y z u v w)) ; -> (1 2 3 3 4 (5 6))
358
359(bind-case '(1 (2 3))
360        ((x (y z)) (list x y z))
361        ((x (y . z)) (list x y z))
362        ((x y) (list x y))) ; -> (1 2 3)
363
364(bind-case '(1 (2 3))
365        ((x (y . z)) (list x y z))
366        ((x y) (list x y))
367        ((x (y z)) (list x y z))) ; -> (1 2 (3))
368
369(bind-case '(1 (2 3))
370        ((x y) (list x y))
371        ((x (y . z)) (list x y z))
372        ((x (y z)) (list x y z))) ; -> (1 (2 3))
373
374(bind-case '(1 (2 . 3))
375        ((x (y . z)) (list x y z))
376        ((x (y z)) (list x y z))) ; -> (1 2 3)
377
378(bind-case '(1 (2 . 3))
379        ((x y) (list x y))
380        ((x (y . z)) (list x y z))
381        ((x (y z)) (list x y z))) ; -> (1 (2 . 3))
382
383</enscript>
384
385== Author
386
387[[/users/juergen-lorenz|Juergen Lorenz]]
388
389== Initial version
390
391Jun, 2011
392
393== Updated
394
395May 18, 2012
396
397== License
398
399 Copyright (c) 2011, Juergen Lorenz
400 All rights reserved.
401
402Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
403
404Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
405
406Redistributions 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.
407
408THIS 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.
409
410== Version History
411
412; 2.0.5 : egg now obsolete, use dbc instead
413; 2.0.4 : internal macro check-em corrected, message arguments in assert now strings
414; 2.0 : (er|ir)-macro-define-with-contract unified to define-macro-with-contract
415; 1.9 : with-aliases and matches? removed from interface, bind and bind-case rewritten
416; 1.8 : Code split into two modules, added matches? with-aliases er-macro-define-with-contract ir-macro-define-with-contract
417; 1.7 : Code fixed to work with new transformer syntax in Chicken-4.7.3, syntax-contract removed as separate macro but retained as literal symbol in define-syntax-with-contract, define-syntax-with-contract rewritten
418; 1.6 : bind and bind-case exported again: needed by ir-macros and er-macros
419; 1.5 : removed bind, bind-case rewritten but removed from interface
420; 1.4 : various chenges: removed unnecessary dependencies, removed bind-let* and matches? from interface, define-syntax-with-contract and bind rewritten, comments updated, ...
421; 1.3 : changed with-literal to literal
422; 1.2 : added bind bind-let* bind-case matches syntax-contract ir-macro-rules er-macro-rules, changed define-syntax-with-contract
423; 1.1 : (results: ...) made obsolete, use (with-results (name ...) . body) within (range: ...) instead
424; 1.0 : changed (effect: ...), removed (state: ...) (invariant: ...)
425; 0.4 : some enhancements
426; 0.3 : added {{print-doclist}}, fixed typo in setup script reported by mario
427; 0.2 : bugfixes
428; 0.1 : initial import
429
Note: See TracBrowser for help on using the repository browser.