ported all of the contracts guide except the examples section
svn: r8242
This commit is contained in:
parent
4ba06b7ae0
commit
1644b86aa8
|
@ -3,7 +3,9 @@
|
||||||
@require[scribble/eval]
|
@require[scribble/eval]
|
||||||
@require["guide-utils.ss"]
|
@require["guide-utils.ss"]
|
||||||
@require["contracts-utils.ss"]
|
@require["contracts-utils.ss"]
|
||||||
@(require (for-label scheme/contract))
|
@(require (for-label scheme/contract)
|
||||||
|
(for-label srfi/13/string)
|
||||||
|
(for-label scheme/gui))
|
||||||
|
|
||||||
@title{Contracts on Functions in General}
|
@title{Contracts on Functions in General}
|
||||||
|
|
||||||
|
@ -13,8 +15,7 @@ You wrote your module. You added contracts. You put them into the interface
|
||||||
so that client programmers have all the information from interfaces. It's a
|
so that client programmers have all the information from interfaces. It's a
|
||||||
piece of art:
|
piece of art:
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[deposit (-> (lambda (x)
|
[deposit (-> (lambda (x)
|
||||||
|
@ -46,8 +47,7 @@ boolean. The "named" part says what we want to do: name the contract so that
|
||||||
error messages become intelligible:
|
error messages become intelligible:
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(define (amount? x) (and (number? x) (integer? x) (>= x 0)))
|
(define (amount? x) (and (number? x) (integer? x) (>= x 0)))
|
||||||
(define amount (flat-named-contract 'amount amount?))
|
(define amount (flat-named-contract 'amount amount?))
|
||||||
|
@ -66,18 +66,220 @@ sudden quite readable:
|
||||||
it had with myaccount on deposit;
|
it had with myaccount on deposit;
|
||||||
expected <amount>, given: -10"}
|
expected <amount>, given: -10"}
|
||||||
|
|
||||||
@question[#:tag "optionals"]{Can a contract specify what the values of optional arguments to a function must be?}
|
@question[#:tag "optional"]{What about optional arguments?}
|
||||||
|
|
||||||
Sure, using the @scheme[->*] contract. For example,
|
Take a look at this excerpt from a string-processing module, inspired by the
|
||||||
|
Scheme cookbook:
|
||||||
|
@schememod[
|
||||||
|
scheme
|
||||||
|
|
||||||
|
(provide/contract
|
||||||
|
;; pad the given str left and right with
|
||||||
|
;; the (optional) char so that it is centered
|
||||||
|
[string-pad-center (->* (string? natural-number/c)
|
||||||
|
(char?)
|
||||||
|
string?)])
|
||||||
|
|
||||||
|
(define (string-pad-center str width [pad #\space])
|
||||||
|
(define field-width (min width (string-length str)))
|
||||||
|
(define rmargin (ceiling (/ (- width field-width) 2)))
|
||||||
|
(define lmargin (floor (/ (- width field-width) 2)))
|
||||||
|
(string-append (build-string lmargin (λ (x) pad))
|
||||||
|
str
|
||||||
|
(build-string rmargin (λ (x) pad))))
|
||||||
|
]
|
||||||
|
|
||||||
|
The module exports @scheme[string-pad-center], a function that creates a
|
||||||
|
string of a given @scheme[width] with the given string in the center. The
|
||||||
|
default fill character is @scheme[#\space]; if the client module requires
|
||||||
|
different character, it may call @scheme[string-pad-center] with a third
|
||||||
|
argument, a @scheme[char], overwriting the default.
|
||||||
|
|
||||||
|
The function definition uses optional arguments of
|
||||||
|
@scheme[lambda], which is appropriate for this kind of
|
||||||
|
functionality. The interesting point here is the formulation
|
||||||
|
of the contract for the @scheme[string-pad-center].
|
||||||
|
|
||||||
|
|
||||||
|
Like the contract combinator for
|
||||||
|
@scheme[->*], i.e., rest arguments, @scheme[opt->] demands several
|
||||||
|
groups of contracts:
|
||||||
|
@itemize{
|
||||||
|
@item{The first one is a parenthesized group of contracts for all required
|
||||||
|
arguments. In this example, we see two: @scheme[string?] and
|
||||||
|
@scheme[natural-number/c]. }
|
||||||
|
|
||||||
|
@item{The second one is a parenthesized group of contracts for all optional
|
||||||
|
arguments: @scheme[char?]. }
|
||||||
|
|
||||||
|
@item{The last one is a single contract: the result of the function.}
|
||||||
|
}
|
||||||
|
Note if a default value does not satisfy a contract, you won't get a contract
|
||||||
|
error for this interface. We do trust you; if you can't trust
|
||||||
|
yourself, you need to communicate across boundaries for everything you write.
|
||||||
|
|
||||||
|
The contract library does not have built-in combinators to
|
||||||
|
specify richer contracts for functions that have optional
|
||||||
|
arguments, like functions that have optional arguments where
|
||||||
|
the arguments depend on each other.
|
||||||
|
|
||||||
|
To specify such contracts combine @scheme[case->] with
|
||||||
|
the other function contract combinators, like we did in
|
||||||
|
the @scheme[substring1] function above.
|
||||||
|
}
|
||||||
|
|
||||||
|
@question[#:tag "rest-args"]{What about rest arguments?}
|
||||||
|
|
||||||
|
We all know that @scheme[+] in Beginner Scheme is a function
|
||||||
|
that consumes at least two numbers but, in principle,
|
||||||
|
arbitraily many more. Defining the function is easy:
|
||||||
|
@schemeblock[
|
||||||
|
(define (plus fst snd . rst)
|
||||||
|
(foldr + (+ fst snd) rst))
|
||||||
|
]
|
||||||
|
Describing this function via a contract is difficult because of the rest
|
||||||
|
argument (@scheme[rst]).
|
||||||
|
|
||||||
|
Here is the contract:
|
||||||
|
@schemeblock[
|
||||||
|
(provide/contract
|
||||||
|
[plus (->* (number? number?) () #:rest (listof number?) number?)])
|
||||||
|
]
|
||||||
|
The @scheme[->*] contract combinator empowers you to specify
|
||||||
|
functions that consume a variable number of arguments or functions like
|
||||||
|
@scheme[plus], which consume "at least this number" of arguments but
|
||||||
|
an arbitrary number of additional arguments.
|
||||||
|
|
||||||
|
The contracts for the required arguments are enclosed in the first
|
||||||
|
pair of parentheses:
|
||||||
|
@schemeblock[
|
||||||
|
(number? number?)
|
||||||
|
]
|
||||||
|
|
||||||
|
For @scheme[plus] they demand two numbers. The empty pair of
|
||||||
|
parenthesis indicates that there are no optional arguments
|
||||||
|
(not counting the rest arguments) and the contract for the
|
||||||
|
rest argument follows @scheme[#:rest]
|
||||||
|
@schemeblock[
|
||||||
|
(listof number?)
|
||||||
|
]
|
||||||
|
Since the remainder of the actual arguments are collected
|
||||||
|
in a list for a rest parameter such as @scheme[rst], the
|
||||||
|
contract demands a list of values; in this specific
|
||||||
|
examples, these values must be number.
|
||||||
|
|
||||||
|
Finally, you may have noticed that the contract for the function range
|
||||||
|
of @scheme[plus] is also wrapped in a pair of parentheses. The reason
|
||||||
|
for those is that functions can return multiple values not just one, and
|
||||||
|
this is our next topic in this guide.
|
||||||
|
|
||||||
|
@question[#:tag "keywords"]{What about keyword arguments?}
|
||||||
|
|
||||||
|
Sometimes, a function accepts many arguments and remembering
|
||||||
|
their order can be a nightmare. To help with such functions,
|
||||||
|
PLT Scheme has @seclink["lambda-keywords"]{keyword} arguments.
|
||||||
|
|
||||||
|
For example, consider this function that creates a simple
|
||||||
|
GUI and asks the user a yes-or-no question:
|
||||||
|
@schememod[
|
||||||
|
scheme/gui
|
||||||
|
|
||||||
|
(define (ask-yes-or-no-question #:question question
|
||||||
|
#:default def
|
||||||
|
#:title title
|
||||||
|
#:width w
|
||||||
|
#:height h)
|
||||||
|
(define d (new dialog% [label title] [width w] [height h]))
|
||||||
|
(define answer def)
|
||||||
|
(define msg (new message% [label question] [parent d]))
|
||||||
|
(define (yes) (set! answer #t) (send d show #f))
|
||||||
|
(define (no) (set! answer #f) (send d show #f))
|
||||||
|
(define yes-b (new button%
|
||||||
|
[label "Yes"]
|
||||||
|
[parent d]
|
||||||
|
[callback (λ (x y) (yes))]
|
||||||
|
[style (if def '(border) '())]))
|
||||||
|
(define no-b (new button%
|
||||||
|
[label "No"]
|
||||||
|
[parent d]
|
||||||
|
[callback (λ (x y) (no))]
|
||||||
|
[style (if def '() '(border))]))
|
||||||
|
(send d show #t)
|
||||||
|
answer)
|
||||||
|
|
||||||
|
(provide/contract
|
||||||
|
[ask-yes-or-no-question
|
||||||
|
(-> #:question string?
|
||||||
|
#:default boolean?
|
||||||
|
#:title string?
|
||||||
|
#:width exact-integer?
|
||||||
|
#:height exact-integer?
|
||||||
|
boolean?)])
|
||||||
|
]
|
||||||
|
@margin-note{Note that if you really want to ask a yes-or-no
|
||||||
|
question via a GUI, you should use
|
||||||
|
@scheme[message-box/custom] (and generally speaking,
|
||||||
|
avoiding the words ``yes'' and ``no'' in your dialog is a
|
||||||
|
good idea, too ...)}
|
||||||
|
|
||||||
|
The contract for @scheme[ask-yes-or-no-question] uses our
|
||||||
|
old friend the @scheme[->] contract combinator. Just like
|
||||||
|
@scheme[lambda] (or @scheme[define]-based functions) use
|
||||||
|
keywords for specifying keyword arguments, it uses keywords
|
||||||
|
for specifying contracts on keyword arguments. In this case,
|
||||||
|
it says that @scheme[ask-yes-or-no-question] must receive
|
||||||
|
five keyword arguments, one for each of the keywords
|
||||||
|
@scheme[#:question],
|
||||||
|
@scheme[#:default],
|
||||||
|
@scheme[#:title],
|
||||||
|
@scheme[#:width], and
|
||||||
|
@scheme[#:height].
|
||||||
|
Also, just like in a function definition, the keywords in
|
||||||
|
the @scheme[->] may appear in any order.
|
||||||
|
|
||||||
|
@question[#:tag "optional-keywords"]{What about optional keyword arguments?}
|
||||||
|
|
||||||
|
Of course, many of the parameters in
|
||||||
|
@scheme[ask-yes-or-no-question] (from the previous question)
|
||||||
|
have reasonable defaults, and should be made optional:
|
||||||
|
@schemeblock[
|
||||||
|
(define (ask-yes-or-no-question #:question question
|
||||||
|
#:default def
|
||||||
|
#:title [title "Yes or No?"]
|
||||||
|
#:width [w 400]
|
||||||
|
#:height [h 200])
|
||||||
|
...)
|
||||||
|
]
|
||||||
|
|
||||||
|
To specify this function's contract, we need to use
|
||||||
|
@scheme[->*]. It too supports keywords just as you might
|
||||||
|
expect, in both the optional and mandatory argument
|
||||||
|
sections. In this case, we have mandatory keywords
|
||||||
|
@scheme[#:question] and @scheme[#:default], and optional keywords
|
||||||
|
@scheme[#:title],
|
||||||
|
@scheme[#:width], and
|
||||||
|
@scheme[#:height]. So, we write the contract like this:
|
||||||
|
@schemeblock[
|
||||||
|
(provide/contract
|
||||||
|
[ask-yes-or-no-question
|
||||||
|
(->* (#:question string?
|
||||||
|
#:default boolean?)
|
||||||
|
|
||||||
|
(#:title string?
|
||||||
|
#:width exact-integer?
|
||||||
|
#:height exact-integer?)
|
||||||
|
|
||||||
|
boolean?)])
|
||||||
|
]
|
||||||
|
putting the mandatory keywords in the first section and the
|
||||||
|
optional ones in the second section.
|
||||||
|
|
||||||
@question[#:tag "arrow-d"]{Can a contract specify that the result depends on the arguments?}
|
@question[#:tag "arrow-d"]{Can a contract specify that the result depends on the arguments?}
|
||||||
|
|
||||||
Here is an excerpt from an imaginary (pardon the pun) numerics module:
|
Here is an excerpt from an imaginary (pardon the pun) numerics module:
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[sqrt.v1 (->d ([argument (>=/c 1)])
|
[sqrt.v1 (->d ([argument (>=/c 1)])
|
||||||
()
|
()
|
||||||
|
@ -87,7 +289,7 @@ scheme/base
|
||||||
|
|
||||||
The contract for the exported function @scheme[sqrt.v1] uses the
|
The contract for the exported function @scheme[sqrt.v1] uses the
|
||||||
@scheme[->d] rather than @scheme[->] function contract. The "d"
|
@scheme[->d] rather than @scheme[->] function contract. The "d"
|
||||||
stands for <em>dependent</em> contract, meaning the contract for the
|
stands for @italic{dependent} contract, meaning the contract for the
|
||||||
function range depends on the value of the argument.
|
function range depends on the value of the argument.
|
||||||
|
|
||||||
In this particular case, the argument of @scheme[sqrt.v1] is greater
|
In this particular case, the argument of @scheme[sqrt.v1] is greater
|
||||||
|
@ -104,77 +306,60 @@ and @scheme[>=/c], and it pays off to look them up in the contract
|
||||||
section of the reference manual. They simplify contracts tremendously
|
section of the reference manual. They simplify contracts tremendously
|
||||||
and make them more accessible to potential clients.
|
and make them more accessible to potential clients.
|
||||||
|
|
||||||
@;{
|
@question[#:tag "arrow-d-args"]{Can a contract specify that arguments depend on each other?}
|
||||||
|
|
||||||
To add: keywords, optional arguments.
|
Eventually bank customers want their money back. Hence, a module that
|
||||||
|
|
||||||
in dependent contracts, discuss what happens when a
|
|
||||||
dependent contract with optional arguments doesn't appear at
|
|
||||||
the call site.
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@;{
|
|
||||||
|
|
||||||
<question title="Can a contract specify that arguments depend on each other?" tag="arrow-r">
|
|
||||||
|
|
||||||
<p>Eventually bank customers want their money back. Hence, a module that
|
|
||||||
implements a bank account must include a method for withdrawing money. Of
|
implements a bank account must include a method for withdrawing money. Of
|
||||||
course, ordinary accounts don't let customers withdraw an arbitrary amount of
|
course, ordinary accounts don't let customers withdraw an arbitrary amount of
|
||||||
money but only as much as they have in the account.
|
money but only as much as they have in the account.
|
||||||
|
|
||||||
<p>Suppose the account module provides the following two functions:
|
Suppose the account module provides the following two functions:
|
||||||
<scheme>
|
@schemeblock[
|
||||||
;; balance : Account -> Amount
|
(code:comment "balance : account -> amount")
|
||||||
;; withdraw : Account Amount -> Account
|
(code:comment "withdraw : account amount -> account")
|
||||||
</scheme>
|
]
|
||||||
Then, informally, the proper precondition for @scheme[withdraw] is that
|
Then, informally, the proper precondition for @scheme[withdraw] is that
|
||||||
<blockquote>
|
``the balance of the given account is greater than or equal to the given (withdrawal) amount.''
|
||||||
"the balance of the given account is greater than or equal to the given (withdrawal) amount."
|
The postcondition is similar to the one for
|
||||||
</blockquote>
|
@questionlink["flat-named-contracts"]{@scheme[deposit]}:
|
||||||
The postcondition is similar to the one for <a
|
``the balance of the resulting account is larger than (or equal to) than the one of the
|
||||||
href="#deposit">@scheme[deposit]</a>:
|
given account.''
|
||||||
<blockquote>
|
|
||||||
"the balance of the resulting account is larger than (or equal to) than the one of the
|
|
||||||
given account."
|
|
||||||
</blockquote>
|
|
||||||
You could of course also formulate a full-fledged correctness condition, namely,
|
You could of course also formulate a full-fledged correctness condition, namely,
|
||||||
that the balance of the resulting account is equal to the balance of the given
|
that the balance of the resulting account is equal to the balance of the given
|
||||||
one, plus the given amount.
|
one, plus the given amount.
|
||||||
|
|
||||||
<p>The following module implements accounts imperatively and specifies the
|
The following module implements accounts imperatively and specifies the
|
||||||
conditions we just discussed:
|
conditions we just discussed:
|
||||||
<table>
|
@schememod[
|
||||||
<tr><td>
|
scheme
|
||||||
<scheme>
|
|
||||||
(module account mzscheme
|
(code:comment "the contract definitions")
|
||||||
(require (lib "contract.ss"))
|
|
||||||
<font color="deeppurple">
|
|
||||||
(define-struct account (balance))
|
(define-struct account (balance))
|
||||||
(define amount natural-number/c)
|
(define amount natural-number/c)
|
||||||
|
|
||||||
(define msg>
|
(define msg> "account a with balance larger than ~a expected")
|
||||||
"account a with balance larger than ~a expected")
|
(define msg< "account a with balance less than ~a expected")
|
||||||
(define msg<
|
|
||||||
"account a with balance less than ~a expected")
|
|
||||||
|
|
||||||
(define (mk-account-contract acc amt op msg)
|
(define (mk-account-contract acc amt op msg)
|
||||||
(define balance0 (balance acc))
|
(define balance0 (balance acc))
|
||||||
(define (ctr a)
|
(define (ctr a)
|
||||||
(and (account? a) (op balance0 (balance a))))
|
(and (account? a) (op balance0 (balance a))))
|
||||||
(flat-named-contract (format msg balance0) ctr))</font>
|
(flat-named-contract (format msg balance0) ctr))
|
||||||
<font color="purple">
|
|
||||||
|
(code:comment "the exports")
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[create (amount . -> . account?)]
|
[create (amount . -> . account?)]
|
||||||
[balance (account? . -> . amount)]
|
[balance (account? . -> . amount)]
|
||||||
[withdraw (->r ([acc account?]
|
[withdraw (->d ([acc account?]
|
||||||
[amt (and/c amount (</c (balance acc)))])
|
[amt (and/c amount (<=/c (balance acc)))])
|
||||||
(mk-account-contract acc amt > msg>))]
|
()
|
||||||
[deposit (->r ([acc account?]
|
[result (mk-account-contract acc amt > msg>)])]
|
||||||
|
[deposit (->d ([acc account?]
|
||||||
[amt amount])
|
[amt amount])
|
||||||
(mk-account-contract acc amt < msg<))])
|
()
|
||||||
</font>
|
[result (mk-account-contract acc amt < msg<)])])
|
||||||
|
|
||||||
|
(code:comment "the function definitions")
|
||||||
(define balance account-balance)
|
(define balance account-balance)
|
||||||
|
|
||||||
(define (create amt) (make-account amt))
|
(define (create amt) (make-account amt))
|
||||||
|
@ -185,163 +370,89 @@ conditions we just discussed:
|
||||||
|
|
||||||
(define (deposit acc amt)
|
(define (deposit acc amt)
|
||||||
(set-account-balance! acc (+ (balance acc) amt))
|
(set-account-balance! acc (+ (balance acc) amt))
|
||||||
acc))
|
acc)
|
||||||
</scheme>
|
]
|
||||||
<td bgcolor="beige" valign="top">
|
|
||||||
<pre>
|
|
||||||
|
|
||||||
|
The second section is the export interface: @itemize{
|
||||||
|
@item{@scheme[create] consumes an initial deposit and
|
||||||
|
produces an account. This kind of contract is just like a
|
||||||
|
type in a statically typed language, except that statically
|
||||||
|
typed languages usually don't support the type ``natural
|
||||||
|
numbers'' (as a full-fledged subtype of numbers). }
|
||||||
|
|
||||||
the contract definitions
|
@item{@scheme[balance] consumes an account and computes its current balance.}
|
||||||
|
|
||||||
|
@item{@scheme[withdraw] consumes an account, named @scheme[acc], and an
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
the exports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
the function definitions
|
|
||||||
|
|
||||||
</pre>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
The purple part is the export interface:
|
|
||||||
<ol>
|
|
||||||
<li>@scheme[create] consumes an initial deposit and produces an
|
|
||||||
account. This kind of contract is just like a type in a statically typed
|
|
||||||
language, except that statically typed languages usually don't support the type
|
|
||||||
"natural numbers" (as a full-fledged subtype of numbers).
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>@scheme[balance] consumes an account and computes its current balance.
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>@scheme[withdraw] consumes an account, named @scheme[acc], and an
|
|
||||||
amount, @scheme[amt]. In addition to being an @scheme[amount], the
|
amount, @scheme[amt]. In addition to being an @scheme[amount], the
|
||||||
latter must also be less than @scheme[(balance acc)], i.e., the balance of
|
latter must also be less than @scheme[(balance acc)], i.e., the balance of
|
||||||
the given account. That is, the contract for @scheme[amt] depends on the
|
the given account. That is, the contract for @scheme[amt] depends on the
|
||||||
value of @scheme[acc], which is what the @scheme[->r] (r for recursive)
|
value of @scheme[acc], which is what the @scheme[->d]
|
||||||
contract combinator expresses.
|
contract combinator expresses.
|
||||||
|
|
||||||
<p>The result contract is formed on the fly: @scheme[(mk-account-contract acc amt
|
The result contract is formed on the fly:
|
||||||
> msg>)]. It is an application of a contract-producing function that
|
@scheme[(mk-account-contract acc amt > msg>)].
|
||||||
|
It is an application of a contract-producing function that
|
||||||
consumes an account, an amount, a comparison operator, and an error message (a
|
consumes an account, an amount, a comparison operator, and an error message (a
|
||||||
format string). The result is a contract.
|
format string). The result is a contract.
|
||||||
</li>
|
}
|
||||||
|
|
||||||
<li>@scheme[deposit]'s contract has been reformulated using the
|
@item{@scheme[deposit]'s contract has been reformulated using the
|
||||||
@scheme[->r] combinator. Strictly speaking, this isn't necessary and the use
|
@scheme[->d] combinator. }
|
||||||
of @scheme[->d] would suffice, because the contracts for the arguments do
|
}
|
||||||
not depend on each other.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
The code in deep purple defines all those pieces that are needed for the
|
The code in the first section defines all those pieces that
|
||||||
formulation of the export contracts: @scheme[account?], @scheme[amount],
|
are needed for the formulation of the export contracts:
|
||||||
error messages (format strings), and @scheme[mk-account-contract]. The
|
@scheme[account?], @scheme[amount], error messages (format
|
||||||
latter is a function that extracts the current balance from the given account
|
strings), and @scheme[mk-account-contract]. The latter is a
|
||||||
and then returns a named contract, whose error message (contract name) is a
|
function that extracts the current balance from the given
|
||||||
string that refers to this balance. The resulting contract checks whether an
|
account and then returns a named contract, whose error
|
||||||
account has a balance that is larger or smaller, depending on the given
|
message (contract name) is a string that refers to this
|
||||||
comparison operator, than the original balance.
|
balance. The resulting contract checks whether an account
|
||||||
|
has a balance that is larger or smaller, depending on the
|
||||||
|
given comparison operator, than the original balance.
|
||||||
|
|
||||||
</question>
|
@question[#:tag "case-lambda"]{What about case-lambda?}
|
||||||
|
|
||||||
<question title="What about rest arguments?" tag="rest-args">
|
Dybvig, in Chapter 5 of the
|
||||||
|
@link["http://www.scheme.com/csug/"]{Chez Scheme User's Guide},
|
||||||
|
explains the meaning and pragmatics of
|
||||||
|
@scheme[case-lambda] with the following example (among
|
||||||
|
others):
|
||||||
|
|
||||||
<p>We all know that @scheme[+] in Beginner Scheme is a function that
|
@schemeblock[
|
||||||
consumes at least two numbers but, in principle, arbitrary
|
|
||||||
manner. Defining the function is easy:
|
|
||||||
<scheme>
|
|
||||||
(define (plus fst snd . rst)
|
|
||||||
(foldr + (+ fst snd) rst))
|
|
||||||
</scheme>
|
|
||||||
Describing this function via a contract is difficult because of the rest
|
|
||||||
argument (@scheme[rst]).
|
|
||||||
|
|
||||||
<p>Here is the contract:
|
|
||||||
<scheme>
|
|
||||||
(provide/contract
|
|
||||||
[plus (->* (number? number?) (listof number?) (number?))])
|
|
||||||
</scheme>
|
|
||||||
The @scheme[->*] contract combinator empowers you to specify
|
|
||||||
functions that consume a variable number of arguments or functions like
|
|
||||||
@scheme[plus], which consume "at least this number" of arguments but
|
|
||||||
an arbitrary number of additional arguments.
|
|
||||||
|
|
||||||
<p>The contracts for the required arguments are enclosed in an additional
|
|
||||||
pair of parentheses:
|
|
||||||
<scheme>
|
|
||||||
(number? number?)
|
|
||||||
</scheme>
|
|
||||||
For @scheme[plus] they demand two numbers. The contract for the
|
|
||||||
rest argument follows:
|
|
||||||
<scheme>
|
|
||||||
(listof number?)
|
|
||||||
</scheme>
|
|
||||||
Since the remainder of the actual arguments are collected in a list for
|
|
||||||
a @scheme[rest] parameter such as @scheme[rst], the contract
|
|
||||||
demands a list of values; in this specific examples, these values must be
|
|
||||||
number.
|
|
||||||
|
|
||||||
<p>Finally, you may have noticed that the contract for the function range
|
|
||||||
of @scheme[plus] is also wrapped in a pair of parentheses. The reason
|
|
||||||
for those is that functions can return multiple values not just one, and
|
|
||||||
this is our next topic in this guide.
|
|
||||||
</question>
|
|
||||||
|
|
||||||
<question title="What about case-lambda?" tag="case-lambda">
|
|
||||||
|
|
||||||
<p><a href="http://www.scheme.com/csug/binding.html">Dybvig</a> explains
|
|
||||||
the meaning and pragmatics of @scheme[case-lambda] with the following
|
|
||||||
example (among others):
|
|
||||||
|
|
||||||
<scheme>
|
|
||||||
(define substring1
|
(define substring1
|
||||||
(case-lambda
|
(case-lambda
|
||||||
[(s) (substring1 s 0 (string-length s))]
|
[(s) (substring1 s 0 (string-length s))]
|
||||||
[(s start) (substring1 s start (string-length s))]
|
[(s start) (substring1 s start (string-length s))]
|
||||||
[(s start end) (substring s start end)]))
|
[(s start end) (substring s start end)]))
|
||||||
</scheme>
|
]
|
||||||
This version of @scheme[substring] has one of the following signature:
|
This version of @scheme[substring] has one of the following signature:
|
||||||
<ol>
|
@itemize{
|
||||||
<li>just a string, in which case it copies the string;
|
@item{just a string, in which case it copies the string;}
|
||||||
<li>a string and an index into the string, in which case it extracts the
|
@item{a string and an index into the string, in which case it extracts the
|
||||||
suffix of the string starting at the index; or
|
suffix of the string starting at the index; or }
|
||||||
<li>a string a start index and an end index, in which case it extracts the
|
@item{a string a start index and an end index, in which case it extracts the
|
||||||
fragment of the string between the two indices.
|
fragment of the string between the two indices. }
|
||||||
</ol>
|
}
|
||||||
|
|
||||||
<p>The contract for such a function is formed with the @scheme[case->]
|
The contract for such a function is formed with the @scheme[case->]
|
||||||
combinator, which combines as many functional contracts as needed:
|
combinator, which combines as many functional contracts as needed:
|
||||||
<scheme>
|
@schemeblock[
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[substring1 (case->
|
[substring1
|
||||||
|
(case->
|
||||||
(string? . -> . string?)
|
(string? . -> . string?)
|
||||||
(string? natural-number/c . -> . string?)
|
(string? natural-number/c . -> . string?)
|
||||||
(string? natural-number/c natural-number/c . -> . string?))])
|
(string? natural-number/c natural-number/c . -> . string?))])
|
||||||
</scheme>
|
]
|
||||||
As you can see, the contract for @scheme[substring1] combines three
|
As you can see, the contract for @scheme[substring1] combines three
|
||||||
function contracts, just as many clauses as the explanation of its
|
function contracts, just as many clauses as the explanation of its
|
||||||
functionality required.
|
functionality required.
|
||||||
|
|
||||||
<p>In the case of @scheme[substring1], we also know that the indices
|
@;{
|
||||||
|
This isn't supported anymore (yet...?). -robby
|
||||||
|
|
||||||
|
In the case of @scheme[substring1], we also know that the indices
|
||||||
that it consumes ought to be natural numbers less than the length of the
|
that it consumes ought to be natural numbers less than the length of the
|
||||||
given string. Since @scheme[case->] just combines arrow contracts,
|
given string. Since @scheme[case->] just combines arrow contracts,
|
||||||
adding such constraints is just a matter of strengthening the individual
|
adding such constraints is just a matter of strengthening the individual
|
||||||
|
@ -362,66 +473,70 @@ comparison operator, than the original balance.
|
||||||
</scheme>
|
</scheme>
|
||||||
Here we used @scheme[->r] to name the parameters and express the
|
Here we used @scheme[->r] to name the parameters and express the
|
||||||
numeric constraints on them.
|
numeric constraints on them.
|
||||||
</question>
|
}
|
||||||
|
|
||||||
|
@question[#:tag "multiple"]{What about multiple values?}
|
||||||
|
|
||||||
<question title="What about multiple values?" tag="multiple">
|
The function @scheme[split] consumes a list of @scheme[char]s
|
||||||
|
|
||||||
<p>The function @scheme[split] consumes a list of @scheme[char]s
|
|
||||||
and delivers the string that occurs before the first occurrence of
|
and delivers the string that occurs before the first occurrence of
|
||||||
@scheme[#\newline] (if any) and the rest of the list:
|
@scheme[#\newline] (if any) and the rest of the list:
|
||||||
<scheme>
|
@schemeblock[
|
||||||
(define (split l)
|
(define (split l)
|
||||||
(define (split l w)
|
(define (split l w)
|
||||||
(cond
|
(cond
|
||||||
[(null? l) (values (list->string (reverse w)) '())]
|
[(null? l) (values (list->string (reverse w)) '())]
|
||||||
[(char=? #\newline (car l)) (values (list->string (reverse w)) (cdr l))]
|
[(char=? #\newline (car l))
|
||||||
|
(values (list->string (reverse w)) (cdr l))]
|
||||||
[else (split (cdr l) (cons (car l) w))]))
|
[else (split (cdr l) (cons (car l) w))]))
|
||||||
(split l '()))
|
(split l '()))
|
||||||
</scheme>
|
]
|
||||||
It is a typical multiple-value function, returning two values by
|
It is a typical multiple-value function, returning two values by
|
||||||
traversing a single list.
|
traversing a single list.
|
||||||
|
|
||||||
<p>
|
|
||||||
The contract for such a function can use the ordinary
|
The contract for such a function can use the ordinary
|
||||||
function arrow @scheme[->], since it
|
function arrow @scheme[->], since it
|
||||||
treats @scheme[values] specially, when it appears as the
|
treats @scheme[values] specially, when it appears as the
|
||||||
last result:
|
last result:
|
||||||
<scheme>
|
@schemeblock[
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[split (-> (listof char?)
|
[split (-> (listof char?)
|
||||||
(values string? (listof char?)))])
|
(values string? (listof char?)))])
|
||||||
</scheme>
|
]
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>The contract for such a function can also be written
|
The contract for such a function can also be written
|
||||||
using @scheme[->*], just like
|
using @scheme[->*], just like @scheme[plus]:
|
||||||
@scheme[plus]:
|
@schemeblock[
|
||||||
<scheme>
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[split (->* ((listof char?))
|
[split (->* ((listof char?))
|
||||||
(string? (listof char?)))])
|
()
|
||||||
</scheme>
|
(values string? (listof char?)))])
|
||||||
As before the contract for the single argument is wrapped
|
]
|
||||||
in an extra pair of parentheses (and must always be wrapped
|
As before the contract for the argument is wrapped in an
|
||||||
like that); so are the contracts for the results: a string
|
extra pair of parentheses (and must always be wrapped like
|
||||||
and a list of characters.
|
that) and the empty pair of parentheses indicates that
|
||||||
</p>
|
there are no optoinal arguments. The contracts for the
|
||||||
|
results are inside @scheme[values]: a string and a list of
|
||||||
|
characters.
|
||||||
|
|
||||||
<p>Now suppose we also want to ensure that the first result of
|
Now suppose we also want to ensure that the first result of
|
||||||
@scheme[split] is a prefix of the given word in list format. In that
|
@scheme[split] is a prefix of the given word in list format. In that
|
||||||
case, we need to combine the @scheme[->*] contract combinator with the
|
case, we need to use the @scheme[->d] contract combinator:
|
||||||
@scheme[->d] combinator, which is of course the @scheme[->d*]
|
@schemeblock[
|
||||||
combinator:
|
(define (substring-of? s)
|
||||||
<scheme>
|
(flat-named-contract
|
||||||
|
(format "substring of ~s" s)
|
||||||
|
(lambda (s2)
|
||||||
|
(and (string? s2)
|
||||||
|
(<= (string-length s2) s)
|
||||||
|
(equal? (substring s 0 (string-length s2)) s2)))))
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[splitp (->d* ((listof char?))
|
[split (->d ([fl (listof char?)])
|
||||||
(lambda (fl)
|
()
|
||||||
(define wd (list->string fl))
|
(values [s (substring-of (list->string fl))]
|
||||||
(values (and/c string? (lambda (w) (string<=? w wd)))
|
[c (listof char?)]))])
|
||||||
(listof char?))))])
|
]
|
||||||
</scheme>
|
Like @scheme[->*], the new combinator uses a function over the
|
||||||
Like @scheme[->d], the new combinator uses a function over the
|
|
||||||
argument to create the range contracts. Yes, it doesn't just return one
|
argument to create the range contracts. Yes, it doesn't just return one
|
||||||
contract but as many as the function produces values: one contract per
|
contract but as many as the function produces values: one contract per
|
||||||
value. In this case, the second contract is the same as before, ensuring
|
value. In this case, the second contract is the same as before, ensuring
|
||||||
|
@ -429,37 +544,32 @@ using @scheme[->*], just like
|
||||||
first contract strengthens the old one so that the result is a prefix of
|
first contract strengthens the old one so that the result is a prefix of
|
||||||
the given word.
|
the given word.
|
||||||
|
|
||||||
<p>This contract is expensive to check of course. Here is a slightly
|
This contract is expensive to check of course. Here is a slightly
|
||||||
cheaper version:
|
cheaper version:
|
||||||
<scheme>
|
@schemeblock[
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[splitl (->d* ((listof char?))
|
[split (->d ([fl (listof char?)])
|
||||||
(lambda (fl)
|
()
|
||||||
(values (and/c string? (string/len (add1 (length fl))))
|
(values [s (string-len/c (length fl))]
|
||||||
(listof char?))))])
|
[c (listof char?)]))])
|
||||||
</scheme>
|
]
|
||||||
Check the help desk for an explanation of @scheme[string/len].
|
Click on @scheme[string-len/c] to see what it does.
|
||||||
|
|
||||||
</question>
|
@question[#:tag "no-domain"]{What about procedures of any specific arity?}
|
||||||
|
|
||||||
<question title="What about procedures of any specific arity?" tag="no-domain">
|
|
||||||
<p>
|
|
||||||
Imagine yourself writing a contract for a function that accepts some other
|
Imagine yourself writing a contract for a function that accepts some other
|
||||||
function and a list of numbers that eventually applies the former to the
|
function and a list of numbers that eventually applies the former to the
|
||||||
latter. Unless the arity of the given function matches the length of the
|
latter. Unless the arity of the given function matches the length of the
|
||||||
given list, your procedure is in trouble.
|
given list, your procedure is in trouble.
|
||||||
</p>
|
|
||||||
|
|
||||||
|
Consider this @scheme[n-step] function:
|
||||||
<p>Consider this @scheme[n-step] function:
|
@schemeblock[
|
||||||
|
(code:comment "(number ... -> (union #f number?)) (listof number) -> void")
|
||||||
<scheme>
|
|
||||||
;; (Number ... -> (union #f number?)) (listof Number) -> Void
|
|
||||||
(define (n-step proc inits)
|
(define (n-step proc inits)
|
||||||
(let ([inc (apply proc inits)])
|
(let ([inc (apply proc inits)])
|
||||||
(when inc
|
(when inc
|
||||||
(n-step proc (map (λ (x) (+ x inc)) inits)))))
|
(n-step proc (map (λ (x) (+ x inc)) inits)))))
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
The argument of @scheme[n-step] is @scheme[proc], a function
|
The argument of @scheme[n-step] is @scheme[proc], a function
|
||||||
@scheme[proc] whose results are either numbers or false, and a list. It
|
@scheme[proc] whose results are either numbers or false, and a list. It
|
||||||
|
@ -467,122 +577,56 @@ then applies @scheme[proc] to the list @scheme[inits]. As long as
|
||||||
@scheme[proc] returns a number, @scheme[n-step] treats that number
|
@scheme[proc] returns a number, @scheme[n-step] treats that number
|
||||||
as an increment for each of the numbers in @scheme[inits] and
|
as an increment for each of the numbers in @scheme[inits] and
|
||||||
recurs. When @scheme[proc] returns @scheme[false], the loop stops.
|
recurs. When @scheme[proc] returns @scheme[false], the loop stops.
|
||||||
</p>
|
|
||||||
|
|
||||||
Here are two uses:
|
Here are two uses:
|
||||||
<table>
|
@schemeblock[
|
||||||
<tr>
|
(code:comment "nat -> nat")
|
||||||
<td width="20" />
|
|
||||||
<td valign="top">
|
|
||||||
<pre>@scheme[
|
|
||||||
;; Nat -> Nat
|
|
||||||
(define (f x)
|
(define (f x)
|
||||||
(printf "~s \n" x)
|
(printf "~s \n" x)
|
||||||
(if (= x 0) #f -1))
|
(if (= x 0) #f -1))
|
||||||
|
|
||||||
(n-step f '(2))
|
(n-step f '(2))
|
||||||
]</pre></td>
|
|
||||||
<td width="150" />
|
(code:comment "nat nat -> nat")
|
||||||
<td valign="top" width="150"><pre>@scheme[
|
|
||||||
;; Nat Nat -> Nat
|
|
||||||
(define (g x y)
|
(define (g x y)
|
||||||
(define z (+ x y))
|
(define z (+ x y))
|
||||||
(printf "~s\n" (list x y z))
|
(printf "~s\n" (list x y z))
|
||||||
(if (= z 0) #f -1))
|
(if (= z 0) #f -1))
|
||||||
|
|
||||||
(n-step g '(1 1))
|
(n-step g '(1 1))
|
||||||
]</pre></td></tr>
|
]
|
||||||
</table>
|
|
||||||
</center>
|
|
||||||
|
|
||||||
<p>A contract for @scheme[n-step] must specify two aspects of
|
A contract for @scheme[n-step] must specify two aspects of
|
||||||
@scheme[proc]'s behavior: its arity must include the number of elements
|
@scheme[proc]'s behavior: its arity must include the number of elements
|
||||||
in @scheme[inits], and it must return either a number of
|
in @scheme[inits], and it must return either a number of
|
||||||
@scheme[#f]. The latter is easy, the former is difficult. At first
|
@scheme[#f]. The latter is easy, the former is difficult. At first
|
||||||
glance, this appears to suggest a contract that assigns a
|
glance, this appears to suggest a contract that assigns a
|
||||||
<em>variable-arity</em> to @scheme[proc]:
|
@italic{variable-arity} to @scheme[proc]:
|
||||||
@scheme[
|
@schemeblock[
|
||||||
(->* ()
|
(->* ()
|
||||||
(listof any/c)
|
(listof any/c)
|
||||||
(or/c number? false/c))
|
(or/c number? false/c))
|
||||||
]
|
]
|
||||||
This contract, however, says that the function must accept <em>any</em>
|
This contract, however, says that the function must accept <em>any</em>
|
||||||
number of arguments, not a <em>specific</em> but
|
number of arguments, not a <em>specific</em> but
|
||||||
<em>undetermined</em> number. Thus, applying @scheme[n-step] to
|
@italic{undetermined} number. Thus, applying @scheme[n-step] to
|
||||||
@scheme[(lambda (x) x)] and @scheme[(list 1)] breaks the contract
|
@scheme[(lambda (x) x)] and @scheme[(list 1)] breaks the contract
|
||||||
because the given function accepts only one argument.
|
because the given function accepts only one argument.
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The correct contract uses the @scheme[unconstrained-domain->]
|
The correct contract uses the @scheme[unconstrained-domain->]
|
||||||
combinator, which specifies only the range of a function, not its
|
combinator, which specifies only the range of a function, not its
|
||||||
domain. It is then possible to combine this contract with an arity test to
|
domain. It is then possible to combine this contract with an arity test to
|
||||||
specify the correct @scheme[n-step]'s contract:
|
specify the correct @scheme[n-step]'s contract:
|
||||||
<scheme>
|
@schemeblock[
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[n-step
|
[n-step
|
||||||
(->r ([proc (and/c (unconstrained-domain-> (or/c false/c number?))
|
(->d ([proc
|
||||||
(λ (f) (procedure-arity-includes? f (length inits))))]
|
(and/c (unconstrained-domain->
|
||||||
|
(or/c false/c number?))
|
||||||
|
(λ (f) (procedure-arity-includes?
|
||||||
|
f
|
||||||
|
(length inits))))]
|
||||||
[inits (listof number?)])
|
[inits (listof number?)])
|
||||||
|
()
|
||||||
any)])
|
any)])
|
||||||
</scheme>
|
]
|
||||||
</p>
|
|
||||||
|
|
||||||
</question>
|
|
||||||
|
|
||||||
<question title="What about opt-lambda?" tag="opt-lambda">
|
|
||||||
|
|
||||||
<p>Take a look at this excerpt from a string-processing module, inspired by the
|
|
||||||
Scheme cookbook:
|
|
||||||
<scheme>
|
|
||||||
(module string-pad mzscheme
|
|
||||||
(require (lib "contract.ss") (lib "etc.ss") (lib "string.ss" "srfi" "13"))
|
|
||||||
|
|
||||||
(provide/contract
|
|
||||||
;; pad the given str left and right with
|
|
||||||
;; the (optional) char so that it is centered
|
|
||||||
[string-pad-center (opt-> (string? natural-number/c) (char?)
|
|
||||||
string?)])
|
|
||||||
|
|
||||||
(define string-pad-center
|
|
||||||
(opt-lambda (str width [pad #\space])
|
|
||||||
(define field-width (min width (string-length str)))
|
|
||||||
(define rmargin (- width (floor (/ (- width field-width) 2))))
|
|
||||||
(string-pad (string-pad-right str rmargin pad) width pad))))
|
|
||||||
</scheme>
|
|
||||||
|
|
||||||
The module exports @scheme[string-pad-center], a function that creates a
|
|
||||||
string of a given @scheme[width] with the given string in the center. The
|
|
||||||
default fill character is @scheme[#\space]; if the client module requires
|
|
||||||
different character, it may call @scheme[string-pad-center] with a third
|
|
||||||
argument, a @scheme[char], overwriting the default.
|
|
||||||
|
|
||||||
<p>The function definition uses @scheme[opt-lambda], which is appropriate
|
|
||||||
for this kind of functionality. The interesting point here is the formulation of
|
|
||||||
the contract for @scheme[opt-lambda]. Like the contract combinator for
|
|
||||||
@scheme[->*], i.e., rest arguments, @scheme[opt->] demands several
|
|
||||||
groups of contracts:
|
|
||||||
<ol>
|
|
||||||
<li>The first one is a parenthesized group of contracts for all required
|
|
||||||
arguments. In this example, we see two: @scheme[string?] and
|
|
||||||
@scheme[natural-number/c].
|
|
||||||
|
|
||||||
<li>The second one is a parenthesized group of contracts for all optional
|
|
||||||
arguments: @scheme[char?].
|
|
||||||
|
|
||||||
<li>The last one is a single contract: the result of the function.
|
|
||||||
</ol>
|
|
||||||
Note if a default value does not satisfy a contract, you won't get a contract
|
|
||||||
error for this interface. We do trust <em>you</em>; if you can't trust
|
|
||||||
yourself, you need to communicate across boundaries for everything you write.
|
|
||||||
|
|
||||||
The contract library does not have built-in combinators to
|
|
||||||
specify richer contracts for functions that have optional
|
|
||||||
arguments, like functions that have optional arguments where
|
|
||||||
the arguments depend on each other.
|
|
||||||
|
|
||||||
To specify such contracts combine @scheme[case->] with
|
|
||||||
the other function contract combinators, like we did in
|
|
||||||
the @scheme[substring1] function above.
|
|
||||||
|
|
||||||
}
|
|
|
@ -5,58 +5,52 @@
|
||||||
@require["contracts-utils.ss"]
|
@require["contracts-utils.ss"]
|
||||||
@(require (for-label scheme/contract))
|
@(require (for-label scheme/contract))
|
||||||
|
|
||||||
<section title="Gotchas" tag="gotchas" />
|
@title{Gotchas}
|
||||||
|
|
||||||
<question> What about @scheme[set!] on variables provided via @scheme[provide/contract]?
|
@question{What about @scheme[set!] on variables provided via @scheme[provide/contract]?}
|
||||||
</question>
|
|
||||||
|
|
||||||
<p>
|
The contract library assumes that variables exported via
|
||||||
The contract library assumes that variables exported
|
@scheme[provide/contract] are not assigned to, but does not
|
||||||
via @scheme[provide/contract] are not assigned to, but
|
enforce it. Accordingly, if you try to @scheme[set!] those
|
||||||
does not enforce it. Accordingly, if you try
|
variables, you may find unexpected behavior. As an example,
|
||||||
to @scheme[set!] those variables, you may find
|
consider this program (running in the MzScheme language of
|
||||||
unexpected behavior. As an example, consider this program:
|
DrScheme):
|
||||||
|
|
||||||
<scheme>
|
@schemeblock[
|
||||||
(module x mzscheme
|
(module server scheme
|
||||||
(require (lib "contract.ss"))
|
|
||||||
(define (inc-x!) (set! x (+ x 1)))
|
(define (inc-x!) (set! x (+ x 1)))
|
||||||
(define x 0)
|
(define x 0)
|
||||||
(provide/contract [inc-x! (-> void?)]
|
(provide/contract [inc-x! (-> void?)]
|
||||||
[x integer?]))
|
[x integer?]))
|
||||||
|
|
||||||
(module client mzscheme
|
(module client scheme
|
||||||
(require x)
|
(require 'server)
|
||||||
|
|
||||||
(define (print-latest) (printf "x is ~s\n" x))
|
(define (print-latest) (printf "x is ~s\n" x))
|
||||||
|
|
||||||
(print-latest)
|
(print-latest)
|
||||||
(inc-x!)
|
(inc-x!)
|
||||||
(print-latest))
|
(print-latest))
|
||||||
|
|
||||||
(require client)
|
(require 'client)
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
When it runs, both calls to @scheme[print-latest]
|
When it runs, both calls to @scheme[print-latest]
|
||||||
print @scheme[0], even though the value
|
print @scheme[0], even though the value
|
||||||
of @scheme[x] has been incremented (and the change is
|
of @scheme[x] has been incremented (and the change is
|
||||||
visible inside the module @scheme[x]).
|
visible inside the module @scheme[x]).
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To work around this, export accessor functions, rather than
|
To work around this, export accessor functions, rather than
|
||||||
exporting the function directly, like this:
|
exporting the function directly, like this:
|
||||||
<scheme>
|
|
||||||
(module x mzscheme
|
@schememod[
|
||||||
(require (lib "contract.ss"))
|
scheme
|
||||||
|
|
||||||
(define (get-x) x)
|
(define (get-x) x)
|
||||||
(define (inc-x!) (set! x (+ x 1)))
|
(define (inc-x!) (set! x (+ x 1)))
|
||||||
(define x 0)
|
(define x 0)
|
||||||
(provide/contract [inc-x! (-> void?)]
|
(provide/contract [inc-x! (-> void?)]
|
||||||
[get-x (-> integer?)]))
|
[get-x (-> integer?)])
|
||||||
|
]
|
||||||
|
|
||||||
</scheme>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
This is a bug we hope to address in a future release.
|
This is a bug we hope to address in a future release.
|
||||||
</p>
|
|
||||||
|
|
|
@ -20,9 +20,7 @@ boundaries. Specifically, programmers may attach contracts to
|
||||||
@scheme[provide] clauses and thus impose constraints and promises on the use
|
@scheme[provide] clauses and thus impose constraints and promises on the use
|
||||||
of exported values. For example, the export specification
|
of exported values. For example, the export specification
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
|
|
||||||
(require scheme/contract) (code:comment "now we can write contracts")
|
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[amount positive?])
|
[amount positive?])
|
||||||
|
@ -34,13 +32,24 @@ positive number. The contract system monitors @scheme[a]'s obligation
|
||||||
carefully. Every time a client refers to @scheme[amount], the monitor checks
|
carefully. Every time a client refers to @scheme[amount], the monitor checks
|
||||||
that the value of @scheme[amount] is indeed a positive number.
|
that the value of @scheme[amount] is indeed a positive number.
|
||||||
|
|
||||||
|
The contracts library is built into the Scheme language, but
|
||||||
|
if you wish to use @scheme[scheme/base], you can explicitly
|
||||||
|
require the contracts library like this:
|
||||||
|
|
||||||
|
@schememod[
|
||||||
|
scheme/base
|
||||||
|
(require scheme/contract) (code:comment "now we can write contracts")
|
||||||
|
|
||||||
|
(provide/contract
|
||||||
|
[amount positive?])
|
||||||
|
(define amount ...)
|
||||||
|
]
|
||||||
|
|
||||||
@question[#:tag "amount0"]{What happens if @scheme[a] sets @scheme[amount] to 0?}
|
@question[#:tag "amount0"]{What happens if @scheme[a] sets @scheme[amount] to 0?}
|
||||||
|
|
||||||
Suppose the creator of @scheme[a] had written
|
Suppose the creator of @scheme[a] had written
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
|
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[amount positive?])
|
[amount positive?])
|
||||||
|
@ -55,7 +64,7 @@ blame @scheme[a] for breaking its promises.
|
||||||
|
|
||||||
Suppose the creator of @scheme[a] had written
|
Suppose the creator of @scheme[a] had written
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[amount positive?])
|
[amount positive?])
|
||||||
|
@ -172,7 +181,7 @@ the module boundary for a second time.
|
||||||
</question>
|
</question>
|
||||||
}
|
}
|
||||||
|
|
||||||
@question[#:tag "obligations"]{How can a "server" module impose obligations on its client?}
|
@question[#:tag "obligations"]{How can a ``server'' module impose obligations on its client?}
|
||||||
|
|
||||||
On occasion, a module may want to enter a contract with
|
On occasion, a module may want to enter a contract with
|
||||||
another module only if the other module abides by certain
|
another module only if the other module abides by certain
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
|
|
||||||
@title[#:tag "contract-func"]{Simple Contracts on Functions}
|
@title[#:tag "contract-func"]{Simple Contracts on Functions}
|
||||||
|
|
||||||
When a module exports a function, it establishes two channels of
|
When a module exports a function, it establishes two
|
||||||
communication between itself and the client module that imports the
|
channels of communication between itself and the client
|
||||||
function. If the client module calls the function, it sends a value into the
|
module that imports the function. If the client module calls
|
||||||
"server" module. Conversely, if such a function call ends and the function
|
the function, it sends a value into the ``server''
|
||||||
returns a value, the "server" module sends a value back to the "client" module.
|
module. Conversely, if such a function call ends and the
|
||||||
|
function returns a value, the ``server'' module sends a
|
||||||
|
value back to the "client" module.
|
||||||
|
|
||||||
It is important to keep this picture in mind when you read the explanations
|
It is important to keep this picture in mind when you read the explanations
|
||||||
of the various ways of imposing contracts on functions.
|
of the various ways of imposing contracts on functions.
|
||||||
|
@ -23,8 +25,7 @@ select subset such as numbers, booleans, etc. Here is a module that may
|
||||||
represent a bank account:
|
represent a bank account:
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[create (-> string? number? any)]
|
[create (-> string? number? any)]
|
||||||
|
@ -39,10 +40,10 @@ It exports two functions:
|
||||||
@itemize{
|
@itemize{
|
||||||
|
|
||||||
@item{@scheme[create]: The function's contract says that it consumes two
|
@item{@scheme[create]: The function's contract says that it consumes two
|
||||||
arguments, a string and a number, and it promises nothing about the return value. }}
|
arguments, a string and a number, and it promises nothing about the return value. }
|
||||||
|
|
||||||
@item{@scheme[deposit]: The function's contract demands from the client modules
|
@item{@scheme[deposit]: The function's contract demands from the client modules
|
||||||
that they apply it to numbers. It promises nothing about the return value. }
|
that they apply it to numbers. It promises nothing about the return value. }}
|
||||||
|
|
||||||
If a "client" module that were to apply @scheme[deposit] to
|
If a "client" module that were to apply @scheme[deposit] to
|
||||||
@scheme['silly], it would violate the contract. The contract monitoring
|
@scheme['silly], it would violate the contract. The contract monitoring
|
||||||
|
@ -56,7 +57,7 @@ the contract monitoring system to check the return value every time the function
|
||||||
is called, even though the "client" module can't do much with this value
|
is called, even though the "client" module can't do much with this value
|
||||||
anyway. In contrast, @scheme[any] tells the monitoring system @italic{not}
|
anyway. In contrast, @scheme[any] tells the monitoring system @italic{not}
|
||||||
to check the return value. Additionally, it tells a potential client that the
|
to check the return value. Additionally, it tells a potential client that the
|
||||||
"server" module @italic{makes no promises at all} about the function's return
|
``server'' module @italic{makes no promises at all} about the function's return
|
||||||
value.
|
value.
|
||||||
|
|
||||||
@question[#:tag "arrow"]{What does the arrow do?}
|
@question[#:tag "arrow"]{What does the arrow do?}
|
||||||
|
@ -130,8 +131,7 @@ To this end, the contract system allows programmers to define their own
|
||||||
contracts:
|
contracts:
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(define (amount? a)
|
(define (amount? a)
|
||||||
(and (number? a) (integer? a) (exact? a) (>= a 0)))
|
(and (number? a) (integer? a) (exact? a) (>= a 0)))
|
||||||
|
@ -164,9 +164,7 @@ a contract defined in "contract.ss" that is equivalent to @scheme[amount]
|
||||||
(modulo the name):
|
(modulo the name):
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
|
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
(code:comment "an amount is a natural number of cents")
|
(code:comment "an amount is a natural number of cents")
|
||||||
|
@ -188,8 +186,7 @@ For example, if we didn't have @scheme[natural-number/c], the
|
||||||
as follows:
|
as follows:
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(define amount
|
(define amount
|
||||||
(and/c number? integer? exact? (or/c positive? zero?)))
|
(and/c number? integer? exact? (or/c positive? zero?)))
|
||||||
|
@ -216,22 +213,24 @@ means? Hint: it is a contract!
|
||||||
Consider a utility module for creating strings from banking records:
|
Consider a utility module for creating strings from banking records:
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
(define (has-decimal? str)
|
||||||
|
(define L (string-length str))
|
||||||
|
(and (>= L 3)
|
||||||
|
(char=?
|
||||||
|
#\.
|
||||||
|
(string-ref result (- L 3)))))
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
...
|
|
||||||
(code:comment "convert a random number to a string")
|
(code:comment "convert a random number to a string")
|
||||||
[format-number (-> number? string?)]
|
[format-number (-> number? string?)]
|
||||||
|
|
||||||
(code:comment "convert an amount into a dollar based string")
|
(code:comment "convert an amount into a dollar based string")
|
||||||
[format-nat (-> natural-number/c
|
[format-nat (-> natural-number/c
|
||||||
(lambda (result)
|
(lambda (result)
|
||||||
(define L (string-length result))
|
|
||||||
(and (string? result)
|
(and (string? result)
|
||||||
(>= L 3)
|
(has-decimal? string))))])
|
||||||
(char=? #\. (string-ref result (- L 3))))))])
|
|
||||||
...
|
|
||||||
]
|
]
|
||||||
The contract of the exported function @scheme[format-number] specifies that
|
The contract of the exported function @scheme[format-number] specifies that
|
||||||
the function consumes a number and produces a string.
|
the function consumes a number and produces a string.
|
||||||
|
@ -241,19 +240,33 @@ interesting than the one of @scheme[format-number]. It consumes only
|
||||||
natural numbers. Its range contract promises a string that has a dot "." in the
|
natural numbers. Its range contract promises a string that has a dot "." in the
|
||||||
third position from the right.
|
third position from the right.
|
||||||
|
|
||||||
@bold{Exercise 1} Strengthen the promise of the range contract for
|
@(exercise) Strengthen the promise of the range contract for
|
||||||
@scheme[format-nat] so that it admits only strings with digits and a single
|
@scheme[format-nat] so that it admits only strings with digits and a single
|
||||||
dot.
|
dot.
|
||||||
|
|
||||||
@question[#:tag "exercise1"]{Solution to Exercise 1}
|
@(solution)
|
||||||
|
|
||||||
@schememod[
|
@schememod[
|
||||||
scheme/base
|
scheme
|
||||||
(require scheme/contract)
|
|
||||||
|
|
||||||
(define (digit-char? x)
|
(define (digit-char? x)
|
||||||
(member x '(#\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\0)))
|
(member x '(#\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\0)))
|
||||||
|
|
||||||
|
(define (has-decimal? str)
|
||||||
|
(define L (string-length str))
|
||||||
|
(and (>= L 3)
|
||||||
|
(char=?
|
||||||
|
#\.
|
||||||
|
(string-ref result (- L 3)))))
|
||||||
|
|
||||||
|
(define (is-decimal-string? str)
|
||||||
|
(define L (string-length str))
|
||||||
|
(and (has-decimal? str)
|
||||||
|
(andmap digit-char?
|
||||||
|
(string->list (substring result 0 (- L 3))))
|
||||||
|
(andmap digit-char?
|
||||||
|
(string->list (substring result (- L 2) L)))))
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
...
|
...
|
||||||
(code:comment "convert a random number to a string")
|
(code:comment "convert a random number to a string")
|
||||||
|
@ -263,13 +276,8 @@ scheme/base
|
||||||
(code:comment "into a dollar based string")
|
(code:comment "into a dollar based string")
|
||||||
[format-nat (-> natural-number/c
|
[format-nat (-> natural-number/c
|
||||||
(lambda (result)
|
(lambda (result)
|
||||||
(define L (string-length result))
|
|
||||||
(and (string? result)
|
(and (string? result)
|
||||||
(andmap digit-char?
|
(is-decimal-string? result))))])
|
||||||
(string->list (substring result 0 (- L 3))))
|
|
||||||
(andmap digit-char?
|
|
||||||
(string->list (substring result (- L 2) L)))
|
|
||||||
(char=? #\. (string-ref result (- L 3))))))])
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,57 +5,50 @@
|
||||||
@require["contracts-utils.ss"]
|
@require["contracts-utils.ss"]
|
||||||
@(require (for-label scheme/contract))
|
@(require (for-label scheme/contract))
|
||||||
|
|
||||||
<section title="Contracts on Structures" tag="structs" />
|
@title{Contracts on Structures}
|
||||||
|
|
||||||
<p>Modules deal with structures in two ways. First they export
|
Modules deal with structures in two ways. First they export
|
||||||
@scheme[struct] definitions, i.e., the ability to create structs of a
|
@scheme[struct] definitions, i.e., the ability to create
|
||||||
certain kind, to access their fields, to modify them, and to distinguish structs
|
structs of a certain kind, to access their fields, to modify
|
||||||
of this kind against every other kind of value in the world. Second, on occasion
|
them, and to distinguish structs of this kind against every
|
||||||
a module exports a specific struct and wishes to promise that its fields contain
|
other kind of value in the world. Second, on occasion a
|
||||||
values of a certain kind. This section explains how to protect structs with
|
module exports a specific struct and wishes to promise that
|
||||||
contracts for both uses.
|
its fields contain values of a certain kind. This section
|
||||||
|
explains how to protect structs with contracts for both
|
||||||
|
uses.
|
||||||
|
|
||||||
<question title="Can a module promise something about a specific struct?" tag="single-struct">
|
@question[#:tag "single-struct"]{Can a module promise something about a specific struct?}
|
||||||
|
|
||||||
<p>Yes. If your module defines a variable to be a structure, then on export you
|
Yes. If your module defines a variable to be a structure, then on export you
|
||||||
can specify the structures shape:
|
can specify the structures shape:
|
||||||
<scheme>
|
@schememod[
|
||||||
(module geometry mzscheme
|
scheme
|
||||||
(require (lib "contract.ss"))
|
(require lang/posn)
|
||||||
(require posn)
|
|
||||||
|
|
||||||
(define origin (make-posn 0 0))
|
(define origin (make-posn 0 0))
|
||||||
...
|
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[origin (struct/c posn zero? zero?)]
|
[origin (struct/c posn zero? zero?)])
|
||||||
...)
|
]
|
||||||
... )
|
|
||||||
</scheme>
|
|
||||||
|
|
||||||
In this example, the module imports a library for representing positions, which
|
In this example, the module imports a library for representing positions, which
|
||||||
exports a @scheme[posn] structure. One of the @scheme[posn]s it creates
|
exports a @scheme[posn] structure. One of the @scheme[posn]s it creates
|
||||||
and exports stands for the origin, i.e., (0,), of the grid.
|
and exports stands for the origin, i.e., @tt{(0,0)}, of the grid.
|
||||||
|
|
||||||
</question>
|
@question[#:tag "single-vector"]{Can a module promise something about a specific vector?}
|
||||||
|
|
||||||
<question title="Can a module promise something about a specific vector?" tag="single-vector">
|
Yes, again. See the help desk for information on @scheme[vector/c] and
|
||||||
|
|
||||||
<p>Yes, again. See the help desk for information on @scheme[vector/c] and
|
|
||||||
similar contract combinators for (flat) compound data.
|
similar contract combinators for (flat) compound data.
|
||||||
|
|
||||||
</question>
|
@question[#:tag "define-struct"]{Can a contract enforce that all structs are well-formed?}
|
||||||
|
|
||||||
<question title="Can a contract enforce that all structs are well-formed?" tag="define-struct">
|
The book @link["http://www.htdp.org/"]{@italic{How to Design
|
||||||
|
Programs}} teaches that @scheme[posn]s should contain only
|
||||||
<p>"How to Design Programs" teaches that @scheme[posn]s should contain only
|
numbers in their two fields. With contracts we would enforce
|
||||||
numbers in their two fields. With contracts we would enforce this informal data
|
this informal data definition as follows:
|
||||||
definition as follows:
|
|
||||||
|
|
||||||
<scheme>
|
|
||||||
(module posn mzscheme
|
|
||||||
(require (lib "contract.ss"))
|
|
||||||
|
|
||||||
|
@schememod[
|
||||||
|
scheme
|
||||||
(define-struct posn (x y))
|
(define-struct posn (x y))
|
||||||
|
|
||||||
(provide/contract
|
(provide/contract
|
||||||
|
@ -64,8 +57,8 @@ definition as follows:
|
||||||
[p-sick posn?])
|
[p-sick posn?])
|
||||||
|
|
||||||
(define p-okay (make-posn 10 20))
|
(define p-okay (make-posn 10 20))
|
||||||
(define p-sick (make-posn 'a 'b)))
|
(define p-sick (make-posn 'a 'b))
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
This module exports the entire structure definition: @scheme[make-posn],
|
This module exports the entire structure definition: @scheme[make-posn],
|
||||||
@scheme[posn?], @scheme[posn-x], @scheme[posn-y],
|
@scheme[posn?], @scheme[posn-x], @scheme[posn-y],
|
||||||
|
@ -73,29 +66,28 @@ This module exports the entire structure definition: @scheme[make-posn],
|
||||||
or promises that the two fields of a @scheme[posn] structure are
|
or promises that the two fields of a @scheme[posn] structure are
|
||||||
numbers---when the values flow across the module boundary.
|
numbers---when the values flow across the module boundary.
|
||||||
|
|
||||||
<p>Thus, if a client calls @scheme[make-posn] on @scheme[10] and
|
Thus, if a client calls @scheme[make-posn] on @scheme[10] and
|
||||||
@scheme['a], the contract system will signal a contract
|
@scheme['a], the contract system will signal a contract
|
||||||
violation. Similarly, if @scheme[(set-posn-x! (make-posn 10 10) 'a)] causes
|
violation.
|
||||||
an error.
|
|
||||||
|
|
||||||
<p>The creation of @scheme[p-sick] inside of the @scheme[posn] module,
|
The creation of @scheme[p-sick] inside of the @scheme[posn] module,
|
||||||
however, does not violate the contracts. The function @scheme[make-posn] is
|
however, does not violate the contracts. The function @scheme[make-posn] is
|
||||||
internal so @scheme['a] and @scheme['b] don't cross the module
|
internal so @scheme['a] and @scheme['b] don't cross the module
|
||||||
boundary. Similarly, when @scheme[p-sick] crosses the boundary of
|
boundary. Similarly, when @scheme[p-sick] crosses the boundary of
|
||||||
@scheme[posn], the contract promises a @scheme[posn?] and nothing
|
@scheme[posn], the contract promises a @scheme[posn?] and nothing
|
||||||
else. In particular, this check does <em>not</em> enforce that the fields of
|
else. In particular, this check does @italic{not} require that the fields of
|
||||||
@scheme[p-sick] are numbers.
|
@scheme[p-sick] are numbers.
|
||||||
|
|
||||||
<p>The association of contract checking with module boundaries implies that
|
The association of contract checking with module boundaries implies that
|
||||||
@scheme[p-okay] and @scheme[p-sick] look alike from a client's
|
@scheme[p-okay] and @scheme[p-sick] look alike from a client's
|
||||||
perspective until the client extracts the pieces:
|
perspective until the client extracts the pieces:
|
||||||
|
|
||||||
<scheme>
|
@schememod[
|
||||||
(module client mzscheme
|
scheme
|
||||||
(require posn)
|
(require lang/posn)
|
||||||
|
|
||||||
... (posn-x p-sick) ...)
|
... (posn-x p-sick) ...
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
Using @scheme[posn-x] is the only way @scheme[client] can find out what
|
Using @scheme[posn-x] is the only way @scheme[client] can find out what
|
||||||
a @scheme[posn] contains in the @scheme[x] field. The application of
|
a @scheme[posn] contains in the @scheme[x] field. The application of
|
||||||
|
@ -105,55 +97,63 @@ perspective until the client extracts the pieces:
|
||||||
system discovers that a promise is broken. Specifically, @scheme[posn-x]
|
system discovers that a promise is broken. Specifically, @scheme[posn-x]
|
||||||
doesn't return a number but a symbol and is therefore blamed.
|
doesn't return a number but a symbol and is therefore blamed.
|
||||||
|
|
||||||
<p>This specific example shows that the explanation for a contract violation
|
This specific example shows that the explanation for a contract violation
|
||||||
doesn't always pinpoint the source of the error. The good news is that the
|
doesn't always pinpoint the source of the error. The good news is that the
|
||||||
error is located in the @scheme[posn] module. The bad news is that the
|
error is located in the @scheme[posn] module. The bad news is that the
|
||||||
explanation is misleading. Although it is true that @scheme[posn-x]
|
explanation is misleading. Although it is true that @scheme[posn-x]
|
||||||
produced a symbol instead of a number, it is the fault of the programmer who
|
produced a symbol instead of a number, it is the fault of the programmer who
|
||||||
created a @scheme[posn] from symbols, i.e., the programmer who added
|
reated a @scheme[posn] from symbols, i.e., the programmer who added
|
||||||
|
|
||||||
<scheme>
|
@schemeblock[
|
||||||
(define p-sick (make-posn 'a 'b))
|
(define p-sick (make-posn 'a 'b))
|
||||||
</scheme>
|
]
|
||||||
to the module. So, when you are looking for bugs based on contract violations,
|
to the module. So, when you are looking for bugs based on contract violations,
|
||||||
keep this example in mind.
|
keep this example in mind.
|
||||||
|
|
||||||
<p><strong>Exercise 2:</strong> Use your knowledge from the <a href="#single-struct">
|
@(exercise) Use your knowledge from the
|
||||||
section on exporting specific structs</a> and change the contract for
|
@questionlink["single-struct"] section on exporting specific
|
||||||
@scheme[p-sick] so that the error is caught when clients refer to the
|
structs and change the contract for @scheme[p-sick] so that
|
||||||
structure. <a href="#exercise2">Solution</a>
|
the error is caught when @scheme[sick] is exported.
|
||||||
|
|
||||||
</question>
|
@(solution)
|
||||||
|
|
||||||
<question title="What about contracts that check properties of data structures?" tag="lazy-contracts">
|
A single change suffices:
|
||||||
|
|
||||||
|
@schemeblock[
|
||||||
|
(provide/contract
|
||||||
|
...
|
||||||
|
[p-sick (struct/c posn number? number?)])
|
||||||
|
]
|
||||||
|
|
||||||
|
Instead of exporting @scheme[p-sick] as a plain @scheme[posn?], we use a
|
||||||
|
@scheme[struct/c] contract to enforce constraints on its components.
|
||||||
|
|
||||||
|
@question[#:tag "lazy-contracts"]{What about contracts that check properties of data structures?}
|
||||||
|
|
||||||
<p>
|
|
||||||
Contracts written using @scheme[struct/c] immediately
|
Contracts written using @scheme[struct/c] immediately
|
||||||
check the fields of the data structure, but sometimes this
|
check the fields of the data structure, but sometimes this
|
||||||
can have disastrous effects on the performance of a program
|
can have disastrous effects on the performance of a program
|
||||||
that does not, itself, inspect the entire data structure.
|
that does not, itself, inspect the entire data structure.
|
||||||
</p>
|
|
||||||
|
|
||||||
<p> As an example, consider the the binary search tree
|
As an example, consider the the binary search tree
|
||||||
search algorithm. A binary search tree is like a binary
|
search algorithm. A binary search tree is like a binary
|
||||||
tree, except that the numbers are organized in the tree to
|
tree, except that the numbers are organized in the tree to
|
||||||
make searching the tree fast. In particular, for each
|
make searching the tree fast. In particular, for each
|
||||||
interior node in the tree, all of the numbers in the left
|
interior node in the tree, all of the numbers in the left
|
||||||
subtree are smaller than the number in the node, and all of
|
subtree are smaller than the number in the node, and all of
|
||||||
the numbers in the right subtree are larger than the number
|
the numbers in the right subtree are larger than the number
|
||||||
in the node. </p>
|
in the node.
|
||||||
|
|
||||||
<p>
|
|
||||||
We can implement implement a search
|
We can implement implement a search
|
||||||
function @scheme[in?] that takes advantage of the
|
function @scheme[in?] that takes advantage of the
|
||||||
structure of the binary search tree.
|
structure of the binary search tree.
|
||||||
<scheme>
|
@schememod[
|
||||||
(module bst mzscheme
|
scheme
|
||||||
(require (lib "contract.ss"))
|
|
||||||
(define-struct node (val left right))
|
(define-struct node (val left right))
|
||||||
|
|
||||||
;; determines if `n' is in the binary search tree `b',
|
(code:comment "determines if `n' is in the binary search tree `b',")
|
||||||
;; exploiting the binary search tree invariant
|
(code:comment "exploiting the binary search tree invariant")
|
||||||
(define (in? n b)
|
(define (in? n b)
|
||||||
(cond
|
(cond
|
||||||
[(null? b) #f]
|
[(null? b) #f]
|
||||||
|
@ -165,7 +165,7 @@ structure of the binary search tree.
|
||||||
[(> n (node-val b))
|
[(> n (node-val b))
|
||||||
(in? n (node-right b))])]))
|
(in? n (node-right b))])]))
|
||||||
|
|
||||||
;; a predicate that identifies binary search trees
|
(code:comment "a predicate that identifies binary search trees")
|
||||||
(define (bst-between? b low high)
|
(define (bst-between? b low high)
|
||||||
(or (null? b)
|
(or (null? b)
|
||||||
(and (<= low (node-val b) high)
|
(and (<= low (node-val b) high)
|
||||||
|
@ -177,13 +177,13 @@ structure of the binary search tree.
|
||||||
(provide (struct node (val left right)))
|
(provide (struct node (val left right)))
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[bst? (any/c . -> . boolean?)]
|
[bst? (any/c . -> . boolean?)]
|
||||||
[in? (number? bst? . -> . boolean?)]))
|
[in? (number? bst? . -> . boolean?)])
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
In a full binary search tree, this means that
|
In a full binary search tree, this means that
|
||||||
the @scheme[in?] function only has to explore a
|
the @scheme[in?] function only has to explore a
|
||||||
logarithmic number of nodes.
|
logarithmic number of nodes.
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The contract on @scheme[in?] guarantees that its input
|
The contract on @scheme[in?] guarantees that its input
|
||||||
is a binary search tree. But a little careful thought
|
is a binary search tree. But a little careful thought
|
||||||
reveals that this contract defeats the purpose of the binary
|
reveals that this contract defeats the purpose of the binary
|
||||||
|
@ -195,18 +195,14 @@ recursive call. Now compare that to the @scheme[bst-between?]
|
||||||
function. In the case that it returns @scheme[#t], it
|
function. In the case that it returns @scheme[#t], it
|
||||||
traverses the entire tree, meaning that the speedup
|
traverses the entire tree, meaning that the speedup
|
||||||
of @scheme[in?] is lost.
|
of @scheme[in?] is lost.
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
In order to fix that, we can employ a new strategy for
|
In order to fix that, we can employ a new strategy for
|
||||||
checking the binary search tree contract. In particular, if
|
checking the binary search tree contract. In particular, if
|
||||||
we only checked the contract on the nodes
|
we only checked the contract on the nodes
|
||||||
that @scheme[in?] looks at, we can still guarantee that
|
that @scheme[in?] looks at, we can still guarantee that
|
||||||
the tree is at least partially well-formed, but without
|
the tree is at least partially well-formed, but without
|
||||||
the performance loss.
|
the performance loss.
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
To do that, we need to
|
To do that, we need to
|
||||||
use @scheme[define-contract-struct] in place
|
use @scheme[define-contract-struct] in place
|
||||||
of @scheme[define-struct]. Like @scheme[define-struct],
|
of @scheme[define-struct]. Like @scheme[define-struct],
|
||||||
|
@ -217,9 +213,7 @@ defines contract combinators, in this
|
||||||
case @scheme[node/c] and @scheme[node/dc]. Also unlike
|
case @scheme[node/c] and @scheme[node/dc]. Also unlike
|
||||||
@scheme[define-struct], it does not define mutators, making
|
@scheme[define-struct], it does not define mutators, making
|
||||||
its structs immutable.
|
its structs immutable.
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The @scheme[node/c] function accepts a contract for each
|
The @scheme[node/c] function accepts a contract for each
|
||||||
field of the struct and returns a contract on the
|
field of the struct and returns a contract on the
|
||||||
struct. More interestingly, the syntactic
|
struct. More interestingly, the syntactic
|
||||||
|
@ -227,16 +221,17 @@ form @scheme[node/dc] allows us to write dependent
|
||||||
contracts, i.e., contracts where some of the contracts on
|
contracts, i.e., contracts where some of the contracts on
|
||||||
the fields depend on the values of other fields. We can use
|
the fields depend on the values of other fields. We can use
|
||||||
this to define the binary search tree contract:
|
this to define the binary search tree contract:
|
||||||
<scheme>
|
@schememod[
|
||||||
(module bst mzscheme (require (lib "contract.ss"))
|
scheme
|
||||||
|
|
||||||
(define-contract-struct node (val left right))
|
(define-contract-struct node (val left right))
|
||||||
|
|
||||||
;; determines if `n' is in the binary search tree `b'
|
(code:comment "determines if `n' is in the binary search tree `b'")
|
||||||
(define (in? n b) ... as before ...)
|
(define (in? n b) ... as before ...)
|
||||||
|
|
||||||
;; bst-between : number number -> contract
|
(code:comment "bst-between : number number -> contract")
|
||||||
;; builds a contract for binary search trees
|
(code:comment "builds a contract for binary search trees")
|
||||||
;; whose values are betweeen low and high
|
(code:comment "whose values are betweeen low and high")
|
||||||
(define (bst-between/c low high)
|
(define (bst-between/c low high)
|
||||||
(or/c null?
|
(or/c null?
|
||||||
(node/dc [val (between/c low high)]
|
(node/dc [val (between/c low high)]
|
||||||
|
@ -248,8 +243,9 @@ this to define the binary search tree contract:
|
||||||
(provide make-node node-left node-right node-val node?)
|
(provide make-node node-left node-right node-val node?)
|
||||||
(provide/contract
|
(provide/contract
|
||||||
[bst/c contract?]
|
[bst/c contract?]
|
||||||
[in? (number? bst/c . -> . boolean?)]))
|
[in? (number? bst/c . -> . boolean?)])
|
||||||
</scheme>
|
]
|
||||||
|
|
||||||
In general, each use of @scheme[node/dc] must name the
|
In general, each use of @scheme[node/dc] must name the
|
||||||
fields and then specify contracts for each fields. In the
|
fields and then specify contracts for each fields. In the
|
||||||
above, the @scheme[val] field is a contract that accepts
|
above, the @scheme[val] field is a contract that accepts
|
||||||
|
@ -263,9 +259,7 @@ this contract ensures the same thing that
|
||||||
the @scheme[bst-between?] function checked in the
|
the @scheme[bst-between?] function checked in the
|
||||||
original example, but here the checking only happens
|
original example, but here the checking only happens
|
||||||
as @scheme[in?] explores the tree.
|
as @scheme[in?] explores the tree.
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Although this contract improves the performance
|
Although this contract improves the performance
|
||||||
of @scheme[in?], restoring it to the logarithmic
|
of @scheme[in?], restoring it to the logarithmic
|
||||||
behavior that the contract-less version had, it is still
|
behavior that the contract-less version had, it is still
|
||||||
|
@ -274,40 +268,12 @@ library also provides @scheme[define-opt/c] that brings
|
||||||
down that constant factor by optimizing its body. Its shape
|
down that constant factor by optimizing its body. Its shape
|
||||||
is just like the @scheme[define] above. It expects its
|
is just like the @scheme[define] above. It expects its
|
||||||
body to be a contract and then optimizes that contract.
|
body to be a contract and then optimizes that contract.
|
||||||
<scheme>
|
|
||||||
|
@schemeblock[
|
||||||
(define-opt/c (bst-between/c low high)
|
(define-opt/c (bst-between/c low high)
|
||||||
(or/c null?
|
(or/c null?
|
||||||
(node/dc [val (between/c low high)]
|
(node/dc [val (between/c low high)]
|
||||||
[left (val) (bst-between/c low val)]
|
[left (val) (bst-between/c low val)]
|
||||||
[right (val) (bst-between/c val high)])))
|
[right (val) (bst-between/c val high)])))
|
||||||
</scheme>
|
]
|
||||||
</p>
|
|
||||||
</question>
|
|
||||||
|
|
||||||
<question title="Solution to Exercise 2" tag="exercise2">
|
|
||||||
|
|
||||||
<p>A single change suffices:
|
|
||||||
|
|
||||||
<scheme>
|
|
||||||
(module posn mzscheme
|
|
||||||
(require (lib "contract.ss"))
|
|
||||||
|
|
||||||
(define I (make-inspector))
|
|
||||||
(provide I)
|
|
||||||
|
|
||||||
(define-struct posn (x y) I)
|
|
||||||
|
|
||||||
(provide/contract
|
|
||||||
[struct posn ((x number?) (y number?))]
|
|
||||||
[p-okay posn?]
|
|
||||||
[p-sick <font color="red">(struct/c posn number? number?)</font>])
|
|
||||||
|
|
||||||
(define p-okay (make-posn 10 20))
|
|
||||||
(define p-sick (make-posn 'a 'b)))
|
|
||||||
</scheme>
|
|
||||||
|
|
||||||
Instead of exporting @scheme[p-sick] as a plain @scheme[posn?], we use a
|
|
||||||
@scheme[struct/c] contract to enforce constraints on its components.
|
|
||||||
|
|
||||||
</question>
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
#lang scheme/base
|
#lang scheme/base
|
||||||
(require scribble/basic)
|
(require scribble/basic
|
||||||
|
scribble/manual)
|
||||||
|
|
||||||
(provide question)
|
(provide question
|
||||||
|
questionlink
|
||||||
|
exercise
|
||||||
|
solution)
|
||||||
|
|
||||||
(define (question #:tag [tag #f] . rest)
|
(define (question #:tag [tag #f] . rest)
|
||||||
(keyword-apply section
|
(keyword-apply section
|
||||||
'(#:tag)
|
'(#:tag)
|
||||||
(list (and tag (format "contracts-~a" tag)))
|
(list (and tag (str->tag tag)))
|
||||||
rest))
|
rest))
|
||||||
|
|
||||||
|
(define (questionlink tag . rest) (apply seclink (str->tag tag) rest))
|
||||||
|
|
||||||
|
(define (str->tag tag) (format "contracts-~a" tag))
|
||||||
|
|
||||||
|
(define exercise-number 0)
|
||||||
|
(define (exercise)
|
||||||
|
(set! exercise-number (+ exercise-number 1))
|
||||||
|
(bold (format "Exercise ~a" exercise-number)))
|
||||||
|
|
||||||
|
(define (solution)
|
||||||
|
(bold (format "Solution to exercise ~a" exercise-number)))
|
|
@ -3,24 +3,39 @@
|
||||||
@require[scribble/eval]
|
@require[scribble/eval]
|
||||||
@require["guide-utils.ss"]
|
@require["guide-utils.ss"]
|
||||||
|
|
||||||
@title[#:tag "contracts"]{Contracts}
|
@title[#:tag "contracts" #:style 'toc]{Contracts}
|
||||||
|
|
||||||
@local-table-of-contents[]
|
@local-table-of-contents[]
|
||||||
|
|
||||||
@;{
|
@;{
|
||||||
|
|
||||||
Somewhere, discuss eq? and its impact on lists and
|
Somewhere, discuss eq? and its impact on lists and
|
||||||
procedures. Also, discuss difference between contracts on
|
procedures.
|
||||||
|
|
||||||
|
Also, discuss difference between contracts on
|
||||||
mutable datastructures & contracts on immutable ones.
|
mutable datastructures & contracts on immutable ones.
|
||||||
|
|
||||||
|
Fill in question on optional arguments in general-function contracts.
|
||||||
|
|
||||||
|
->d and dependency (commented out section in general contracts).
|
||||||
|
|
||||||
|
update string-pad-center to show examples via REPL notation:
|
||||||
|
|
||||||
|
(string-pad-center "nba" 10)
|
||||||
|
(code:comment "=> \" abc \"")
|
||||||
|
|
||||||
|
(string-pad-center "nba" 10 #\-)
|
||||||
|
(code:comment "=> \"---abc----\"")
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include-section["contracts-intro.scrbl"]
|
@include-section["contracts-intro.scrbl"]
|
||||||
@include-section["contracts-simple-function.scrbl"]
|
@include-section["contracts-simple-function.scrbl"]
|
||||||
@include-section["contracts-general-function.scrbl"]
|
@include-section["contracts-general-function.scrbl"]
|
||||||
@;{
|
|
||||||
@include-section["contracts-structure.scrbl"]
|
@include-section["contracts-structure.scrbl"]
|
||||||
|
@;{
|
||||||
@include-section["contracts-class.scrbl"]
|
@include-section["contracts-class.scrbl"]
|
||||||
@include-section["contracts-example.scrbl"]
|
@include-section["contracts-example.scrbl"]
|
||||||
@include-section["contract-gotchas.scrbl"]
|
|
||||||
}
|
}
|
||||||
|
@include-section["contracts-gotchas.scrbl"]
|
||||||
|
|
|
@ -116,6 +116,10 @@ Like @scheme[</c], but for @scheme[<=].}
|
||||||
@defproc[(>=/c [n number?]) flat-contract?]{
|
@defproc[(>=/c [n number?]) flat-contract?]{
|
||||||
Like @scheme[</c], but for @scheme[>=].}
|
Like @scheme[</c], but for @scheme[>=].}
|
||||||
|
|
||||||
|
@defproc[(between/c [n number?] [m number?])
|
||||||
|
flat-contract?]{ Returns a flat contract that requires the
|
||||||
|
input to be a between @scheme[n] and @scheme[m] or equal to
|
||||||
|
one of them.}
|
||||||
|
|
||||||
@defproc[(real-in [n real?][m real?]) flat-contract?]{
|
@defproc[(real-in [n real?][m real?]) flat-contract?]{
|
||||||
|
|
||||||
|
@ -134,7 +138,7 @@ between @scheme[j] and @scheme[k], inclusive.}
|
||||||
A flat contract that requires the input to be an exact non-negative integer.}
|
A flat contract that requires the input to be an exact non-negative integer.}
|
||||||
|
|
||||||
|
|
||||||
@defproc[(string/len [len nonnegative-exact-integer?]) flat-contract?]{
|
@defproc[(string-len/c [len nonnegative-exact-integer?]) flat-contract?]{
|
||||||
|
|
||||||
Returns a flat contract that recognizes strings that have fewer than
|
Returns a flat contract that recognizes strings that have fewer than
|
||||||
@scheme[len] characters.}
|
@scheme[len] characters.}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user