more rackty guide, through the contracts section

This commit is contained in:
Matthew Flatt 2010-04-29 17:11:31 -06:00
parent 7ed0d4e00a
commit 2f5b166073
10 changed files with 761 additions and 752 deletions

View File

@ -6,7 +6,7 @@
(for-label racket/contract)
(for-label racket/gui))
@title[#:tag "contracts-examples"]{Examples}
@title[#:tag "contracts-examples"]{Additional Examples}
This section illustrates the current state of Racket's contract
implementation with a series of examples from @italic{Design by
@ -59,7 +59,7 @@ Note: To mimic Mitchell and McKim's informal notion of parametericity
places, this use of first-class contracts improves on Mitchell and McKim's
design (see comments in interfaces).
@section{A Customer Manager Component for Managing Customer Relationships}
@section{A Customer-Manager Component}
This first module contains some struct definitions in a
separate module in order to better track bugs.

View File

@ -3,29 +3,29 @@
scribble/eval
"guide-utils.ss"
"contracts-utils.ss"
(for-label scheme/contract))
(for-label racket/contract))
@title[#:tag "contracts-exists"]{Abstract Contracts using @scheme[#:exists] and @scheme[#:∃]}
@title[#:tag "contracts-exists"]{Abstract Contracts using @racket[#:exists] and @racket[#:∃]}
The contract system provides existential contracts that can
protect abstractions, ensuring that clients of your module
cannot depend on the precise representation choices you make
for your data structures.
@ctc-section{Getting Started, with a Stack Example}
#; @ctc-section{Getting Started, with a Stack Example}
@margin-note{
You can type @scheme[#:exists] instead of @scheme[#:∃] if you
cannot easily type unicode characters; in DrScheme, typing
You can type @racket[#:exists] instead of @racket[#:∃] if you
cannot easily type unicode characters; in DrRacket, typing
@tt{\exists} followed by either alt-\ or control-\ (depending
on your platform) will produce @scheme[∃].}
The @scheme[provide/contract] form allows you to write
@schemeblock[#:∃ _name-of-a-new-contract] as one of its clauses. This declaration
introduces the variable @scheme[_name-of-a-new-contract], binding it to a new
on your platform) will produce @racket[∃].}
The @racket[provide/contract] form allows you to write
@racketblock[#:∃ _name-of-a-new-contract] as one of its clauses. This declaration
introduces the variable @racket[_name-of-a-new-contract], binding it to a new
contract that hides information about the values it protects.
As an example, consider this (simple) implementation of a stack datastructure:
@schememod[scheme
@racketmod[racket
(define empty '())
(define (enq top queue) (append queue (list top)))
(define (next queue) (car queue))
@ -39,14 +39,14 @@ As an example, consider this (simple) implementation of a stack datastructure:
[deq (-> (listof integer?) (listof integer?))]
[empty? (-> (listof integer?) boolean?)])]
This code implements a queue purely in terms of lists, meaning that clients
of this data structure might use @scheme[car] and @scheme[cdr] directly on the
of this data structure might use @racket[car] and @racket[cdr] directly on the
data structure (perhaps accidentally) and thus any change in the representation
(say to a more efficient representation that supports amortized constant time
enqueue and dequeue operations) might break client code.
To ensure that the stack representation is abstact, we can use @scheme[#:∃] in the
@scheme[provide/contract] expression, like this:
@schemeblock[(provide/contract
To ensure that the stack representation is abstact, we can use @racket[#:∃] in the
@racket[provide/contract] expression, like this:
@racketblock[(provide/contract
#:∃ stack
[empty stack]
[enq (-> integer? stack stack)]
@ -54,7 +54,7 @@ To ensure that the stack representation is abstact, we can use @scheme[#:∃] in
[deq (-> stack (listof integer?))]
[empty? (-> stack boolean?)])]
Now, if clients of the data structure try to use @scheme[car] and @scheme[cdr], they
Now, if clients of the data structure try to use @racket[car] and @racket[cdr], they
receive an error, rather than mucking about with the internals of the queues.
See also @ctc-link["exists-gotcha"].

View File

@ -9,66 +9,15 @@
@title[#:tag "contracts-general-functions"]{Contracts on Functions in General}
@ctc-section[#:tag "flat-named-contracts"]{Contract Error Messages that Contain ``???''}
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
piece of art:
@racketmod[
racket
(provide/contract
[deposit (-> (lambda (x)
(and (number? x) (integer? x) (>= x 0)))
any)])
(define this 0)
(define (deposit a) ...)
]
Several clients used your module. Others used their
modules in turn. And all of a sudden one of them sees this error
message:
@inset-flow{@racketerror{bank-client broke the contract (-> ??? any)
it had with myaccount on deposit; expected <???>, given: -10}}
Clearly, @racket[bank-client] is a module that uses @racket[myaccount]
but what is the @racketerror{???} doing there? Wouldn't it be nice if
we had a name for this class of data much like we have string, number,
and so on?
For this situation, Racket provides @deftech{flat named
contracts}. The use of ``contract'' in this term shows that contracts
are first-class values. The ``flat'' means that the collection of data
is a subset of the built-in atomic classes of data; they are described
by a predicate that consumes all Racket values and produces a
boolean. The ``named'' part says what we want to do, which is to name
the contract so that error messages become intelligible:
@racketmod[
racket
(define (amount? x) (and (number? x) (integer? x) (>= x 0)))
(define amount (flat-named-contract 'amount amount?))
(provide/contract
[deposit (amount . -> . any)])
(define this 0)
(define (deposit a) ...)
]
With this little change, the error message becomes all of the
sudden quite readable:
@inset-flow{@racketerror{bank-client broke the contract (-> amount
any) it had with myaccount on deposit; expected <amount>, given: -10}}
The @racket[->] contract constructor works for functions that take a
fixed number of arguments and where the result contract is independent
of the input arguments. To support other kinds of functions, Racket
supplies additional contract constructors, notable @racket[->].
@ctc-section[#:tag "optional"]{Optional Arguments}
Take a look at this excerpt from a string-processing module, inspired by the
@link["http://racketcookbook.org"]{Racket cookbook}:
@link["http://schemecookbook.org"]{Scheme cookbook}:
@racketmod[
racket
@ -104,6 +53,7 @@ point here is the formulation of the contract for the
The contract combinator @racket[->*], 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: @racket[string?] and
@ -115,64 +65,57 @@ arguments: @racket[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. In contrast
to type systems, we do trust you; if you can't trust
yourself, you need to communicate across boundaries for
everything you write.
Note if a default value does not satisfy a contract, you won't get a
contract error for this interface. If you can't trust yourself to get
the initial value right, you need to communicate the initial value
across a boundary.
@ctc-section[#:tag "rest-args"]{Rest Arguments}
We all know that @racket[+] in Beginner Racket is a function
that consumes at least two numbers but, in principle,
arbitrarily many more. Defining the function is easy:
@racketblock[
(define (plus fst snd . rst)
(foldr + (+ fst snd) rst))
]
Describing this function via a contract is difficult because of the rest
argument (@racket[rst]).
The @racket[max] operator consumes at least one real number, but it
accepts any number of additional arguments. You can write other such
functions using a ``rest'' argument, such as in @racket[max-abs]:
@margin-note{See @secref["rest-args"] for an introduction to rest
arguments.}
@racketblock[
(define (max-abs n . rst)
(foldr (lambda (n m) (max (abs n) m)) (abs n) rst))
]
Describing this function through a contract requires a further
extension of @racket[->*]: a @racket[#:rest] keyword specifies a
contract on a list of arguments after the required and optional
arguments:
Here is the contract:
@racketblock[
(provide/contract
[plus (->* (number? number?) () #:rest (listof number?) number?)])
]
The @racket[->*] contract combinator empowers you to specify
functions that consume a variable number of arguments or functions like
@racket[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:
@racketblock[
(number? number?)
[max-abs (->* (real?) () #:rest (listof real?) real?)])
]
For @racket[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 @racket[#:rest]
@racketblock[
(listof number?)
]
Since the remainder of the actual arguments are collected
in a list for a rest parameter such as @racket[rst], the
contract demands a list of values; in this specific
examples, these values must be numbers.
As always for @racket[->*], the contracts for the required arguments
are enclosed in the first pair of parentheses, which in this case is a
single real number. The empty pair of parenthesis indicates that there
are no optional arguments (not counting the rest arguments). The
contract for the rest argument follows @racket[#:rest]; since all
additional arguments must be real numbers, the list of rest arguments
must satisfy the contract @racket[(listof real?)].
@ctc-section[#:tag "keywords"]{Keyword Arguments}
Sometimes, a function accepts many arguments and remembering
their order can be a nightmare. To help with such functions,
Racket has @seclink["lambda-keywords"]{keyword} arguments.
It turns out that the @racket[->] contract constructor also contains
support for keyword arguments. For example, consider this function,
which creates a simple GUI and asks the user a yes-or-no question:
@margin-note{See @secref["lambda-keywords"] for an introduction to
keyword arguments.}
For example, consider this function that creates a simple
GUI and asks the user a yes-or-no question:
@racketmod[
racket/gui
(define (ask-yes-or-no-question #:question question
(define (ask-yes-or-no-question question
#:default answer
#:title title
#:width w
@ -194,41 +137,43 @@ racket/gui
(provide/contract
[ask-yes-or-no-question
(-> #:question string?
(-> 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
@racket[message-box/custom] (and generally speaking,
avoiding the responses ``yes'' and ``no'' in your dialog is a
good idea, too ...).}
The contract for @racket[ask-yes-or-no-question] uses our
old friend the @racket[->] contract combinator. Just like
@racket[lambda] (or @racket[define]-based functions) use
keywords for specifying keyword arguments, it uses keywords
for specifying contracts on keyword arguments. In this case,
it says that @racket[ask-yes-or-no-question] must receive
five keyword arguments, one for each of the keywords
@racket[#:question],
@margin-note{If you really want to ask a yes-or-no question
via a GUI, you should use @racket[message-box/custom]. For that
matter, it's usually better to provide buttons with more specific
answers than ``yes'' and ``no.''}
The contract for @racket[ask-yes-or-no-question] uses @racket[->], and
in the same way that @racket[lambda] (or @racket[define]-based
functions) allows a keyword to precede a functions formal argument,
@racket[->] allows a keyword to precede a function contract's argument
contract. In this case,
the contract says that @racket[ask-yes-or-no-question] must receive four keyword
arguments, one for each of the keywords
@racket[#:default],
@racket[#:title],
@racket[#:width], and
@racket[#:height].
Also, just like in a function definition, the keywords in
the @racket[->] may appear in any order.
As in a function definition, the order of the keywords in @racket[->]
relative to each other does not matter for clients of the function;
only the relative order of argument contracts without keywords
matters.
@ctc-section[#:tag "optional-keywords"]{Optional Keyword Arguments}
Of course, many of the parameters in
@racket[ask-yes-or-no-question] (from the previous question)
have reasonable defaults, and should be made optional:
have reasonable defaults and should be made optional:
@racketblock[
(define (ask-yes-or-no-question #:question question
(define (ask-yes-or-no-question question
#:default answer
#:title [title "Yes or No?"]
#:width [w 400]
@ -237,17 +182,18 @@ have reasonable defaults, and should be made optional:
]
To specify this function's contract, we need to use
@racket[->*]. It too supports keywords just as you might
expect, in both the optional and mandatory argument
sections. In this case, we have mandatory keywords
@racket[#:question] and @racket[#:default], and optional keywords
@racket[->*] again. It supports keywords just as you might
expect in both the optional and mandatory argument
sections. In this case, we have the mandatory keyword
@racket[#:default] and optional keywords
@racket[#:title],
@racket[#:width], and
@racket[#:height]. So, we write the contract like this:
@racketblock[
(provide/contract
[ask-yes-or-no-question
(->* (#:question string?
(->* (string?
#:default boolean?)
(#:title string?
@ -256,71 +202,182 @@ sections. In this case, we have mandatory keywords
boolean?)])
]
putting the mandatory keywords in the first section and the
optional ones in the second section.
@ctc-section[#:tag "arrow-d"]{When a Function's Result Depends on its Arguments}
That is, we put the mandatory keywords in the first section, and we
put the optional ones in the second section.
Here is an excerpt from an imaginary (pardon the pun) numerics module:
@racketmod[
racket
(provide/contract
[sqrt.v1 (->d ([argument (>=/c 1)])
()
[result (<=/c argument)])])
...
@ctc-section[#:tag "case-lambda"]{Contracts for @racket[case-lambda]}
A function defined with @racket[case-lambda] might impose different
constraints on its arguments depending on how many are provided. For
example, a @racket[report-cost] function might convert either a pair
or numbers or a string into a new string:
@margin-note{See @secref["case-lambda"] for an introduction to
@racket[case-lambda].}
@def+int[
(define report-cost
(case-lambda
[(lo hi) (format "between $~a and $~a" lo hi)]
[(desc) (format "~a of dollars" desc)]))
(report-cost 5 8)
(report-cost "millions")
]
The contract for the exported function @racket[sqrt.v1] uses the
@racket[->d] rather than @racket[->] function contract. The ``d''
stands for @italic{dependent} contract, meaning the contract for the
function range depends on the value of the argument.
The contract for such a function is formed with the @racket[case->]
combinator, which combines as many functional contracts as needed:
@racketblock[
(provide/contract
[report-cost
(case->
(integer? integer? . -> . string?)
(string? . -> . string?))])
]
As you can see, the contract for @racket[report-cost] combines two
function contracts, which is just as many clauses as the explanation
of its functionality required.
In this particular case, the argument of @racket[sqrt.v1] is greater
or equal to 1. Hence a very basic correctness check is that the result is
smaller than the argument. (Naturally, if this function is critical, one
could strengthen this check with additional clauses.)
@;{
This isn't supported anymore (yet...?). -robby
In the case of @racket[substring1], we also know that the indices
that it consumes ought to be natural numbers less than the length of the
given string. Since @racket[case->] just combines arrow contracts,
adding such constraints is just a matter of strengthening the individual
contracts:
<racket>
(provide/contract
[substring1 (case->
(string? . -> . string?)
(->r ([s string?]
[_ (and/c natural-number/c (</c (string-length s)))])
string?)
(->r ([s string?]
[a (and/c natural-number/c (</c (string-length s)))]
[o (and/c natural-number/c
(>=/c a)
(</c (string-length s)))])
string?))])
</racket>
Here we used @racket[->r] to name the parameters and express the
numeric constraints on them.
}
@ctc-section[#:tag "arrow-d"]{Argument and Result Dependencies}
The following is an excerpt from an imaginary numerics module:
@racketblock[
(provide/contract
[real-sqrt (->d ([argument (>=/c 1)])
()
[result (<=/c argument)])])
]
The contract for the exported function @racket[real-sqrt] uses the
@racket[->d] rather than @racket[->*] function contract. The ``d''
stands for a @italic{dependent} contract, meaning the contract for the
function range depends on the value of the argument. In this
particular case, the argument of @racket[real-sqrt] is greater or
equal to 1, so a very basic correctness check is that the result is
smaller than the argument.
In general, a dependent function contract looks just like
the more general @racket[->*] contract, but with names added
that can be used elsewhere in the contract.
@;{
Yes, there are many other contract combinators such as @racket[<=/c]
and @racket[>=/c], and it pays off to look them up in the contract
section of the reference manual. They simplify contracts tremendously
and make them more accessible to potential clients.
}
@ctc-section[#:tag "arrow-d-args"]{When Contract Arguments Depend on Each Other}
Going back to the back-account example, suppose that we generalize the
module to support multiple accounts and that we also include a
withdrawal operation. The improved bank-account module includes a
@racket[account] structure type and the following functions:
Eventually bank customers want their money back. Hence, a module that
implements a bank account must include a method for withdrawing money. Of
course, ordinary accounts don't let customers withdraw an arbitrary amount of
money but only as much as they have in the account.
Suppose the account module provides the following two functions:
@racketblock[
balance : (-> account amount)
withdraw : (-> account amount account)
(provide/contract
[balance (-> account? amount/c)]
[withdraw (-> account? amount/c account?)]
[deposit (-> account? amount/c account?)])
]
Then, informally, the proper precondition for @racket[withdraw] is that
``the balance of the given account is greater than or equal to the given (withdrawal) amount.''
The postcondition is similar to the one for
@ctc-link["flat-named-contracts"]{@racket[deposit]}:
``the balance of the resulting account is larger than (or equal to) the one of the
given account.''
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
one, plus the given amount.
The following module implements accounts imperatively and specifies the
conditions we just discussed:
Besides requiring that a client provide a valid amount for a
withdrawal, however, the amount should be less than the specified
account's balance, and the resulting account will have less money than
it started with. Similarly, the module might promise that a deposit
produces an account with money added to the account. The following
implementation enforces those constraints and guarantees through
contracts:
@racketmod[
racket
(code:comment "section 1: the contract definitions")
(define-struct account (balance) #:mutable)
(define amount natural-number/c)
(struct account (balance))
(define amount/c natural-number/c)
(code:comment "section 2: the exports")
(provide/contract
[create (amount/c . -> . account?)]
[balance (account? . -> . amount/c)]
[withdraw (->d ([acc account?]
[amt (and/c amount/c (<=/c (balance acc)))])
()
[result (and/c account?
(lambda (res)
(>= (balance res)
(- (balance acc) amt))))])]
[deposit (->d ([acc account?]
[amt amount/c])
()
[result (and/c account?
(lambda (res)
(>= (balance res)
(+ (balance acc) amt))))])])
(code:comment "section 3: the function definitions")
(define balance account-balance)
(define (create amt) (account amt))
(define (withdraw a amt)
(account (- (account-balance a) amt)))
(define (deposit a amt)
(account (+ (account-balance a) amt)))
]
The contracts in section 2 provide typical type-like guarantees for
@racket[create] and @racket[balance]. For @racket[withdraw] and
@racket[deposit], however, the contracts check and guarantee the more
complicated constraints on @racket[balance] and @racket[deposit]. The
contract on the second argument to @racket[withdraw] uses
@racket[(balance acc)] to check whether the supplied withdrawal amount
is small enough, where @racket[acc] is the name given within
@racket[->d] to the function's first argument. The contract on the
result of @racket[withdraw] uses both @racket[acc] and @racket[amt] to
guarantee that no more than that requested amount was withdrawn. The
contract on @racket[deposit] similarly uses @racket[acc] and
@racket[amount] in the result contract to guarantee that at least as
much money as provided was deposited into the account.
As written above, when a contract check fails, the error message is
not great. The following revision uses @racket[flat-named-contract]
within a helper function @racket[mk-account-contract] to provide
better error messages.
@racketmod[
racket
(code:comment "section 1: the contract definitions")
(struct account (balance))
(define amount/c natural-number/c)
(define msg> "account a with balance larger than ~a expected")
(define msg< "account a with balance less than ~a expected")
@ -333,70 +390,30 @@ racket
(code:comment "section 2: the exports")
(provide/contract
[create (amount . -> . account?)]
[balance (account? . -> . amount)]
[create (amount/c . -> . account?)]
[balance (account? . -> . amount/c)]
[withdraw (->d ([acc account?]
[amt (and/c amount (<=/c (balance acc)))])
[amt (and/c amount/c (<=/c (balance acc)))])
()
[result (mk-account-contract acc amt >= msg>)])]
[deposit (->d ([acc account?]
[amt amount])
[amt amount/c])
()
[result (mk-account-contract acc amt <= msg<)])])
(code:comment "section 3: the function definitions")
(define balance account-balance)
(define (create amt) (make-account amt))
(define (create amt) (account amt))
(define (withdraw acc amt)
(set-account-balance! acc (- (balance acc) amt))
acc)
(define (withdraw a amt)
(account (- (account-balance a) amt)))
(define (deposit acc amt)
(set-account-balance! acc (+ (balance acc) amt))
acc)
(define (deposit a amt)
(account (+ (account-balance a) amt)))
]
The second section is the export interface: @itemize[
@item{@racket[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). }
@item{@racket[balance] consumes an account and computes its current balance.}
@item{@racket[withdraw] consumes an account, named @racket[acc], and an
amount, @racket[amt]. In addition to being an @racket[amount], the
latter must also be less than @racket[(balance acc)], i.e., the balance of
the given account. That is, the contract for @racket[amt] depends on the
value of @racket[acc], which is what the @racket[->d]
contract combinator expresses.
The result contract is formed on the fly:
@racket[(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
format string). The result is a contract.
}
@item{@racket[deposit]'s contract has been reformulated using the
@racket[->d] combinator. }
]
The code in the first section defines all those pieces that
are needed for the formulation of the export contracts:
@racket[account?], @racket[amount], error messages (format
strings), and @racket[mk-account-contract]. The latter is a
function that extracts the current balance from the given
account and then returns a named contract, whose error
message (contract name) is a string that refers to this
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.
@ctc-section[#:tag "arrow-d-eval-order"]{Ensuring that a Function Properly Modifies State}
@ctc-section[#:tag "arrow-d-eval-order"]{Checking State Changes}
The @racket[->d] contract combinator can also ensure that a
function only modifies state according to certain
@ -451,70 +468,6 @@ contrast, if the contract for @racket[f] were
(only changing the underscore to @racket[res]), then
the result of @racket[get-x] would be @racket['(ctc f)].
@ctc-section[#:tag "case-lambda"]{Contracts for @racket[case-lambda]}
Dybvig, in Chapter 5 of the
@link["http://www.racket.com/csug/"]{Chez Racket User's Guide},
explains the meaning and pragmatics of
@racket[case-lambda] with the following example (among
others):
@racketblock[
(define substring1
(case-lambda
[(s) (substring1 s 0 (string-length s))]
[(s start) (substring1 s start (string-length s))]
[(s start end) (substring s start end)]))
]
This version of @racket[substring] has one of the following signature:
@itemize[
@item{just a string, in which case it copies the string;}
@item{a string and an index into the string, in which case it extracts the
suffix of the string starting at the index; or }
@item{a string a start index and an end index, in which case it extracts the
fragment of the string between the two indices. }
]
The contract for such a function is formed with the @racket[case->]
combinator, which combines as many functional contracts as needed:
@racketblock[
(provide/contract
[substring1
(case->
(string? . -> . string?)
(string? natural-number/c . -> . string?)
(string? natural-number/c natural-number/c . -> . string?))])
]
As you can see, the contract for @racket[substring1] combines three
function contracts, just as many clauses as the explanation of its
functionality required.
@;{
This isn't supported anymore (yet...?). -robby
In the case of @racket[substring1], we also know that the indices
that it consumes ought to be natural numbers less than the length of the
given string. Since @racket[case->] just combines arrow contracts,
adding such constraints is just a matter of strengthening the individual
contracts:
<racket>
(provide/contract
[substring1 (case->
(string? . -> . string?)
(->r ([s string?]
[_ (and/c natural-number/c (</c (string-length s)))])
string?)
(->r ([s string?]
[a (and/c natural-number/c (</c (string-length s)))]
[o (and/c natural-number/c
(>=/c a)
(</c (string-length s)))])
string?))])
</racket>
Here we used @racket[->r] to name the parameters and express the
numeric constraints on them.
}
@ctc-section[#:tag "multiple"]{Multiple Result Values}
The function @racket[split] consumes a list of @racket[char]s
@ -534,8 +487,8 @@ The function @racket[split] consumes a list of @racket[char]s
traversing a single list.
The contract for such a function can use the ordinary
function arrow @racket[->], since it
treats @racket[values] specially, when it appears as the
function arrow @racket[->], since @racket[->]
treats @racket[values] specially when it appears as the
last result:
@racketblock[
(provide/contract
@ -544,21 +497,21 @@ last result:
]
The contract for such a function can also be written
using @racket[->*], just like @racket[plus]:
using @racket[->*]:
@racketblock[
(provide/contract
[split (->* ((listof char?))
()
(values string? (listof char?)))])
]
As before the contract for the argument is wrapped in an
As before, the contract for the argument with @racket[->*] is wrapped in an
extra pair of parentheses (and must always be wrapped like
that) and the empty pair of parentheses indicates that
there are no optoinal arguments. The contracts for the
there are no optional arguments. The contracts for the
results are inside @racket[values]: a string and a list of
characters.
Now suppose we also want to ensure that the first result of
Now, suppose that we also want to ensure that the first result of
@racket[split] is a prefix of the given word in list format. In that
case, we need to use the @racket[->d] contract combinator:
@racketblock[
@ -584,7 +537,7 @@ Now suppose we also want to ensure that the first result of
first contract strengthens the old one so that the result is a prefix of
the given word.
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:
@racketblock[
(provide/contract
@ -593,9 +546,9 @@ This contract is expensive to check of course. Here is a slightly
(values [s (string-len/c (length fl))]
[c (listof char?)]))])
]
Click on @racket[string-len/c] to see what it does.
@ctc-section[#:tag "no-domain"]{Procedures of Some Fixed, but Statically Unknown Arity}
@ctc-section[#:tag "no-domain"]{Fixed but Statically Unknown Arities}
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

View File

@ -1,33 +1,33 @@
#lang scribble/doc
@(require scribble/manual
scribble/eval
scheme/sandbox
racket/sandbox
"guide-utils.ss"
"contracts-utils.ss"
(for-label scheme/contract))
(for-label racket/contract))
@title[#:tag "contracts-gotchas"]{Gotchas}
@ctc-section{Contracts and @scheme[eq?]}
@ctc-section{Contracts and @racket[eq?]}
As a general rule, adding a contract to a program should
either leave the behavior of the program unchanged, or
should signal a contract violation. And this is almost true
for PLT Scheme contracts, with one exception: @scheme[eq?].
for Racket contracts, with one exception: @racket[eq?].
The @scheme[eq?] procedure is designed to be fast and does
The @racket[eq?] procedure is designed to be fast and does
not provide much in the way of guarantees, except that if it
returns true, it means that the two values behave
identically in all respects. Internally, this is implemented
as pointer equality at a low-level so it exposes information
about how PLT Scheme is implemented (and how contracts are
about how Racket is implemented (and how contracts are
implemented).
Contracts interact poorly with @scheme[eq?] because function
Contracts interact poorly with @racket[eq?] because function
contract checking is implemented internally as wrapper
functions. For example, consider this module:
@schememod[
scheme
@racketmod[
racket
(define (make-adder x)
(if (= 1 x)
@ -36,51 +36,51 @@ scheme
(provide/contract [make-adder (-> number? (-> number? number?))])
]
It exports the @scheme[make-adder] function that is the usual curried
addition function, except that it returns Scheme's @scheme[add1] when
its input is @scheme[1].
It exports the @racket[make-adder] function that is the usual curried
addition function, except that it returns Racket's @racket[add1] when
its input is @racket[1].
You might expect that
@schemeblock[
@racketblock[
(eq? (make-adder 1)
(make-adder 1))
]
would return @scheme[#t], but it does not. If the contract were
changed to @scheme[any/c] (or even @scheme[(-> number? any/c)]), then
the @scheme[eq?] call would return @scheme[#t].
would return @racket[#t], but it does not. If the contract were
changed to @racket[any/c] (or even @racket[(-> number? any/c)]), then
the @racket[eq?] call would return @racket[#t].
Moral: do not use @scheme[eq?] on values that have contracts.
Moral: Do not use @racket[eq?] on values that have contracts.
@ctc-section[#:tag "exists-gotcha"]{Exists Contracts and Predicates}
Much like the @scheme[eq?] example above, @scheme[#:∃] contracts
Much like the @racket[eq?] example above, @racket[#:∃] contracts
can change the behavior of a program.
Specifically,
the @scheme[null?] predicate (and many other predicates) return @scheme[#f]
for @scheme[#:∃] contracts, and changing one of those contracts to @scheme[any/c]
means that @scheme[null?] might now return @scheme[#t] instead, resulting in
the @racket[null?] predicate (and many other predicates) return @racket[#f]
for @racket[#:∃] contracts, and changing one of those contracts to @racket[any/c]
means that @racket[null?] might now return @racket[#t] instead, resulting in
arbitrarily different behavior depending on this boolean might flow around
in the program.
@defmodulelang[scheme/exists]
@defmodulelang[racket/exists]
To work around the above problem, the
@schememodname[scheme/exists] library behaves just like the @schememodname[scheme],
but where predicates signal errors when given @scheme[#:∃] contracts.
@racketmodname[racket/exists] library behaves just like the @racketmodname[racket],
but where predicates signal errors when given @racket[#:∃] contracts.
Moral: do not use predicates on @scheme[#:∃] contracts, but if you're not sure, use
@schememodname[scheme/exists] to be safe.
Moral: Do not use predicates on @racket[#:∃] contracts, but if you're not sure, use
@racketmodname[racket/exists] to be safe.
@ctc-section{Defining Recursive Contracts}
When defining a self-referential contract, it is natural to use
@scheme[define]. For example, one might try to write a contract on
@racket[define]. For example, one might try to write a contract on
streams like this:
@(define e (make-base-eval))
@(interaction-eval #:eval e (require scheme/contract))
@(interaction-eval #:eval e (require racket/contract))
@interaction[
#:eval e
(define stream/c
@ -92,12 +92,12 @@ streams like this:
@close-eval[e]
Unfortunately, this does not work because the value of
@scheme[stream/c] is needed before it is defined. Put another way, all
@racket[stream/c] is needed before it is defined. Put another way, all
of the combinators evaluate their arguments eagerly, even thought the
values that they accept do not.
Instead, use
@schemeblock[
@racketblock[
(define stream/c
(promise/c
(or/c
@ -106,27 +106,27 @@ Instead, use
(recursive-contract stream/c)))))
]
The use of @scheme[recursive-contract] delays the evaluation of the
identifier @scheme[stream/c] until after the contract is first
checked, long enough to ensure that @scheme[stream/c] is defined.
The use of @racket[recursive-contract] delays the evaluation of the
identifier @racket[stream/c] until after the contract is first
checked, long enough to ensure that @racket[stream/c] is defined.
See also @ctc-link["lazy-contracts"].
@ctc-section{Using @scheme[set!] to Assign to Variables Provided via @scheme[provide/contract]}
@ctc-section{Mixing @racket[set!] and @racket[provide/contract]}
The contract library assumes that variables exported via
@scheme[provide/contract] are not assigned to, but does not enforce
it. Accordingly, if you try to @scheme[set!] those variables, you
@racket[provide/contract] are not assigned to, but does not enforce
it. Accordingly, if you try to @racket[set!] those variables, you
may be surprised. Consider the following example:
@interaction[
(module server scheme
(module server racket
(define (inc-x!) (set! x (+ x 1)))
(define x 0)
(provide/contract [inc-x! (-> void?)]
[x integer?]))
(module client scheme
(module client racket
(require 'server)
(define (print-latest) (printf "x is ~s\n" x))
@ -138,15 +138,15 @@ may be surprised. Consider the following example:
(require 'client)
]
Both calls to @scheme[print-latest] print @scheme[0], even though the
value of @scheme[x] has been incremented (and the change is visible
inside the module @scheme[x]).
Both calls to @racket[print-latest] print @racket[0], even though the
value of @racket[x] has been incremented (and the change is visible
inside the module @racket[x]).
To work around this, export accessor functions, rather than
exporting the variable directly, like this:
@schememod[
scheme
@racketmod[
racket
(define (get-x) x)
(define (inc-x!) (set! x (+ x 1)))
@ -155,5 +155,5 @@ scheme
[get-x (-> integer?)])
]
Moral: This is a bug we hope to address in a future release.
Moral: This is a bug that we will address in a future release.

View File

@ -3,7 +3,7 @@
scribble/eval
"guide-utils.ss"
"contracts-utils.ss"
(for-label scheme/contract))
(for-label racket/contract))
@title[#:tag "contract-boundaries"]{Contracts and Boundaries}
@ -16,72 +16,73 @@ A contract thus establishes a boundary between the two parties. Whenever a
value crosses this boundary, the contract monitoring system performs contract
checks, making sure the partners abide by the established contract.
In this spirit, PLT Scheme supports contracts only at module
In this spirit, Racket encourages contracts mainly at module
boundaries. Specifically, programmers may attach contracts to
@scheme[provide] clauses and thus impose constraints and promises on the use
@racket[provide] clauses and thus impose constraints and promises on the use
of exported values. For example, the export specification
@schememod[
scheme
@racketmod[
racket
(provide/contract
[amount positive?])
(define amount ...)
]
promises to all clients of the above module that amount will
promises to all clients of the above module that the value of @racket[amount] will
always be a positive number. The contract system monitors
the module's obligation carefully. Every time a client
refers to @scheme[amount], the monitor checks that the value
of @scheme[amount] is indeed a positive number.
refers to @racket[amount], the monitor checks that the value
of @racket[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
The contracts library is built into the Racket language, but
if you wish to use @racket[racket/base], you can explicitly
require the contracts library like this:
@schememod[
scheme/base
(require scheme/contract) (code:comment "now we can write contracts")
@racketmod[
racket/base
(require racket/contract) (code:comment "now we can write contracts")
(provide/contract
[amount positive?])
(define amount ...)
]
@ctc-section[#:tag "amount0"]{A First Contract Violation}
@ctc-section[#:tag "amount0"]{Contract Violations}
Suppose the creator of the module had written
@schememod[
scheme
If we bind @scheme[amount] to a number that is not positive,
@racketmod[
racket
(provide/contract
[amount positive?])
[amount positive?])
(define amount 0)]
When this module is required, the monitoring
then, when the module is required, the monitoring
system signals a violation of the contract and
blames the module for breaking its promises.
@ctc-section[#:tag "qamount"]{A Subtle Contract Violation}
@; @ctc-section[#:tag "qamount"]{A Subtle Contract Violation}
Suppose we write this module
@schememod[
scheme
An even bigger mistake would be to bind @racket[amount]
to a non-number value:
@racketmod[
racket
(provide/contract
[amount positive?])
[amount positive?])
(define amount 'amount)
]
In that case, the monitoring system applies
@scheme[positive?] to a symbol, but @scheme[positive?]
In this case, the monitoring system will apply
@racket[positive?] to a symbol, but @racket[positive?]
reports an error, because its domain is only numbers. To
make the contract capture our intentions for all Scheme
make the contract capture our intentions for all Racket
values, we can ensure that the value is both a number and is
positive, combining the two contracts with @scheme[and/c]:
positive, combining the two contracts with @racket[and/c]:
@schemeblock[
@racketblock[
(provide/contract
[amount (and/c number? positive?)])
]
@ -95,13 +96,13 @@ provide/contract'd. This is currently buggy so this
discussion is elided. Here's the expansion of
the requiring module, just to give an idea:
(module m mzscheme
(module m racket
(require mzlib/contract)
(provide/contract [x x-ctc]))
(module n mzscheme (require m) (define (f) ... x ...))
(module n racket (require m) (define (f) ... x ...))
==>
(module n mzscheme
(module n racket
(require (rename m x x-real))
(define x (apply-contract x-real x-ctc ...))
(define (f) ... x ...))
@ -122,9 +123,9 @@ Of course, this breaks assignment to the provided variable.
<table src="simple.ss">
<tr><td bgcolor="e0e0fa">
<scheme>
<racket>
;; Language: Pretty Big
(module a mzscheme
(module a racket
(require mzlib/contract)
(provide/contract
@ -139,7 +140,7 @@ Of course, this breaks assignment to the provided variable.
(define (do-it) <font color="red">(set! amount -4)</font>))
(module b mzscheme
(module b racket
(require a)
(printf "~s~n" amount)
@ -147,17 +148,17 @@ Of course, this breaks assignment to the provided variable.
(printf "~s~n" amount))
(require b)
</scheme>
</racket>
<td bgcolor="beige" valign="top">
<pre>
the "server" module
this allows us to write contracts
export @scheme[amount] with a contract
export @racket[amount] with a contract
export @scheme[do-it] without contract
export @racket[do-it] without contract
@ -168,65 +169,71 @@ set amount to 4,
the "client" module
requires functionality from a
first reference to @scheme[amount] (okay)
a call to @scheme[do-it],
second reference to @scheme[amount] (fail)
first reference to @racket[amount] (okay)
a call to @racket[do-it],
second reference to @racket[amount] (fail)
</pre> </table>
<p><strong>Note:</strong> The above example is mostly self-explanatory. Take a
look at the lines in red, however. Even though the call to @scheme[do-it]
sets @scheme[amount] to -4, this action is <strong>not</strong> a contract
look at the lines in red, however. Even though the call to @racket[do-it]
sets @racket[amount] to -4, this action is <strong>not</strong> a contract
violation. The contract violation takes place only when the client module
(@scheme[b]) refers to @scheme[amount] again and the value flows across
(@racket[b]) refers to @racket[amount] again and the value flows across
the module boundary for a second time.
</question>
}
@;{
@ctc-section[#:tag "obligations"]{Imposing Obligations on a Module's Clients}
On occasion, a module may want to enter a contract with
another module only if the other module abides by certain
rules. In other words, the module isn't just promising some
services, it also demands the client to deliver
something. This kind of thing happens when a module exports
a function, an object, a class or other values that enable
something. That situation may happen when a module exports
a function, an object, a class, or some other construct that enables
values to flow in both directions.
@ctc-section{Experimenting with Examples}
}
@ctc-section{Experimenting with Contracts and Modules}
All of the contracts and module in this chapter (excluding those just
following) are written using the standard @tt{#lang} syntax for
describing modules. Thus, if you extract examples from this chapter in
order to experiment with the behavior of the contract system, you
would have to make multiple files.
describing modules. Since modules serve as the boundary between
parties in a contract, examples involve multiple modules.
To rectify this, PLT Scheme provides a special language, called
@schememodname[scheme/load]. The contents of such a module is other modules (and
@scheme[require] statements), using the parenthesized syntax for a
module. For example, to try the example earlier in this section, you
would write:
@schememod[
scheme/load
To experiment with multiple modules within a single module or within
DrRacket's @tech{definitions area}, use the
@racketmodname[racket/load] language. The contents of such a module
can be other modules (and @racket[require] statements), using the
longhand parenthesized syntax for a module (see
@secref["module-syntax"]). For example, try the example earlier in
this section as follows:
(module m scheme
(define amount 150)
(provide/contract [amount (and/c number? positive?)]))
@racketmod[
racket/load
(module n scheme
(module m racket
(provide/contract [amount (and/c number? positive?)])
(define amount 150))
(module n racket
(require 'm)
(+ amount 10))
(require 'n)]
Each of the modules and their contracts are wrapped in parentheses
with the @scheme[module] keyword at the front. The first argument to
@scheme[module] should be the name of the module, so it can be used in
a subsequent @scheme[require] statement (note that in the
@scheme[require], the name of the module must be prefixed with a
quote). The second argument to @scheme[module] is the language (what
would have come after @tt{#lang} in the usual notation), and the
remaining arguments are the body of the module. After all of the
modules, there must a @scheme[require] to kick things off.
with the @racket[module] keyword at the front. The first form after
@racket[module] is the name of the module to be used in a subsequent
@racket[require] statement (where each reference through a
@racket[require] prefixes the name with a quote). The second form
after @racket[module] is the language, and the remaining forms are the
body of the module. After all of the modules, a @racket[require]
starts one of the modules plus anything that is @racket[require]s.

View File

@ -3,136 +3,161 @@
scribble/eval
"guide-utils.ss"
"contracts-utils.ss"
(for-label scheme/contract))
(for-label racket/contract))
@title[#:tag "contract-func"]{Simple Contracts on Functions}
When a module exports a function, it establishes two
channels of communication between itself and the client
module that imports the function. If the client module calls
the function, it sends a value into the ``server''
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.
A mathematical function has a @deftech{domain} and a
@deftech{range}. The domain indicates the kind of values that the
function can accept as arguments, and the range indicates the kind of
values that it produces. The conventional notation for a describing a
function with its domain and range is
It is important to keep this picture in mind when you read the explanations
of the various ways of imposing contracts on functions.
@ctc-section[#:tag "argcontract"]{Restricting the Arguments of a Function}
Functions usually don't work on all possible Scheme values but only on a
select subset such as numbers, booleans, etc. Here is a module that may
represent a bank account:
@schememod[
scheme
(provide/contract
[create (-> string? number? any)]
[deposit (-> number? any)])
(define amount 0)
(define (create name initial-deposit) ...)
(define (deposit a) (set! amount (+ amount a)))
]
It exports two functions:
@itemize[
@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. }
@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. }]
If a ``client'' module were to apply @scheme[deposit] to
@scheme['silly], it would violate the contract. The
contract monitoring system would catch this violation and
blame ``client'' for breaking the contract with the above
module.
@bold{Note:} Instead of @scheme[any] you could also use the
more specific contract @scheme[void?], which says that the function will
always return the @scheme[(void)] value. This contract, however, would require
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
anyway. In contrast, @scheme[any] tells the monitoring system @italic{not}
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
value.
@ctc-section[#:tag "arrow"]{Arrows}
It is natural to use an arrow to say that an exported value is a
function. In decent high schools, you learn that a function has a domain
and a range, and that you write this fact down like this:
@schemeblock[
@racketblock[
f : A -> B
]
Here the @scheme[A] and @scheme[B] are sets; @scheme[A] is the
domain and @scheme[B] is the range.
Functions in a programming language have domains and ranges, too. In
statically typed languages, you write down the names of types for each
argument and for the result. When all you have, however, is a Scheme name,
such as @scheme[create] or @scheme[deposit], you want to tell the
reader what the name represents (a function) and, if it is a function (or
some other complex value) what the pieces are supposed to be. This is why
we use a @scheme[->] to say ``hey, expect this to be a function.''
where @racket[A] is the domain of the function and @racket[B] is the
range.
So @scheme[->] says ``this is a contract for a function.'' What follows
in a function contracts are contracts (sub-contracts if you wish) that tell
the reader what kind of arguments to expect and what kind of a result the
function produces. For example,
@schemeblock[
Functions in a programming language have domains and ranges, too, and
a contract can ensure that a function receives only values in its
range and produces only values in its domain. A @racket[->] creates
such a contract for a function. The forms after a @racket[->] specify
contracts for the domains and finally a contract for the range.
Here is a module that might represent a bank account:
@racketmod[
racket
(provide/contract
[deposit (-> number? any)]
[balance (-> number?)])
(define amount 0)
(define (deposit a) (set! amount (+ amount a)))
(define (balance) amount)
]
The module exports two functions:
@itemize[
@item{@racket[deposit], which accepts a number and returns some value
that is not specified in the contract, and}
@item{@racket[balance], which returns a number indicating the current
balance of the account.}
]
When a module exports a function, it establishes two channels of
communication between itself as a ``server'' and the ``client'' module
that imports the function. If the client module calls the function, it
sends a value into the server 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. This client--server distinction is
important, because when something goes wrong, one or the other of the
parties is to blame.
If a client module were to apply @racket[deposit] to @racket['millions],
it would violate the contract. The contract-monitoring system would
catch this violation and blame client for breaking the contract with
the above module. In contrast, if the @racket[balance] function were
to return @racket['broke], the contract-monitoring system
would blame the server module.
A @racket[->] by itself is not a contract; it is a @deftech{contract
combinator}, which combines other contracts to form a contract.
@; ------------------------------------------------------------------------
@section{Styles of @racket[->]}
If you are used to mathematical function, you may prefer a contract
arrow to appear between the domain and the range of a function, not
at the beginning. If you have read @|HtDP|, you have seen this many
times. Indeed, you may have seen contracts such as these in other
people's code:
@racketblock[
(provide/contract
[create (-> string? number? boolean? account?)])
]
says that @scheme[create] is a function of three arguments: a string, a
number, and a boolean. Its result is an account.
In short, the arrow @scheme[->] is a @italic{contract
combinator}. Its purpose is to combine other contracts into a contract
that says ``this is a function @italic{and} its arguments and its result
are like that.''
@ctc-section[#:tag "dots"]{Infix Contract Notation}
If you are used to mathematics, you like the arrow in between the
domain and the range of a function, not at the beginning. If you
have read @|HtDP|, you have seen this many times. Indeed, you may
have seen contracts such as these in other people's code:
@schemeblock[
(provide/contract
[create (string? number? boolean? . -> . account?)])
[deposit (number? . -> . any)])
]
If a PLT Scheme S-expression contains two dots with a symbol in the middle,
the reader re-arranges the S-expression and place the symbol at the front. Thus,
@schemeblock[
(string? number? boolean? . -> . account?)
If a Racket S-expression contains two dots with a symbol in the middle,
the reader re-arranges the S-expression and place the symbol at the front,
as described in @secref["lists-and-syntax"]. Thus,
@racketblock[
(number? . -> . any)
]
is really just a short-hand for
@schemeblock[
(-> string? number? boolean? account?)
is just another way of writing
@racketblock[
(-> number? any)
]
Of course, placing the arrow to the left of the range follows not only
mathematical tradition but also that of typed functional languages.
@ctc-section[#:tag "own"]{Rolling Your Own Contracts for Function Arguments}
@; ----------------------------------------------------------------------
@section{@racket[any] and @racket[any/c]}
The @scheme[deposit] function adds the given number to the value of
@scheme[amount]. While the function's contract prevents clients from
applying it to non-numbers, the contract still allows them to apply the function
to complex numbers, negative numbers, or inexact numbers, all of which do not
represent amounts of money.
The @racket[any] contract used for @racket[deposit] matches any kind
of result, and it can only be used in the range position of a function
contract. Instead of @racket[any] above, we could use the more
specific contract @racket[void?], which says that the function will
always return the @racket[(void)] value. The @racket[void?] contract,
however, would require 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 the value. In contrast,
@racket[any] tells the monitoring system @italic{not} to check the
return value, it tells a potential client that the ``server'' module
@italic{makes no promises at all} about the function's return value,
even whether it is a single value or multiple values.
To this end, the contract system allows programmers to define their own
contracts:
The @racket[any/c] contract is similar to @racket[any], in that it
makes no demands on a value. Unlike @scheme[any], @racket[any/c]
indicates a single value, and it is suitable for use as an argument
contract. Using @racket[any/c] as a range contract imposes a check
that the function produces a single value. That is,
@schememod[
scheme
@racketblock[(-> integer? any)]
describes a function that accepts and integer and returns any number of
values, while
@racketblock[(-> integer? any/c)]
describes a function that accepts an integer and produces a single
result (but does not say anything more about the result). The function
@racketblock[
(define (f x) (values (+ x 1) (- x 1)))
]
matches @racket[(-> integer? any)], but not @racket[(-> integer? any/c)].
Use @racket[any/c] as a result contract when it is particularly
important to promise a single result from a function. Use @racket[any]
when you want to promise as little as possible (and incur as little
checking as possible) for a function's result.
@; ------------------------------------------------------------------------
@ctc-section[#:tag "own"]{Rolling Your Own Contracts}
The @racket[deposit] function adds the given number to the value of
@racket[amount]. While the function's contract prevents clients from
applying it to non-numbers, the contract still allows them to apply
the function to complex numbers, negative numbers, or inexact numbers,
none of which sensibly represent amounts of money.
The contract system allows programmers to define their own contracts
as functions:
@racketmod[
racket
(define (amount? a)
(and (number? a) (integer? a) (exact? a) (>= a 0)))
@ -141,80 +166,66 @@ scheme
(code:comment "an amount is a natural number of cents")
(code:comment "is the given number an amount?")
[deposit (-> amount? any)]
[amount? (-> any/c boolean?)])
[amount? (-> any/c boolean?)]
[balance (-> amount?)])
(define this 0)
(define (deposit a) (set! this (+ this a)))
(define amount 0)
(define (deposit a) (set! amount (+ amount a)))
(define (balance) amount)
]
The module introduces a
predicate, @scheme[amount?]. The @scheme[provide]
clause refers to this predicate, as a contract, for its
specification of the contract of
@scheme[deposit].
This module define an @racket[amount?] function as uses it as a
contract within @racket[->] contracts. When a client calls the
@racket[deposit] function as exported with the contract @racket[(->
amount? any)], it must supply an exact, nonnegative integer, otherwise
the @racket[amount?] function applied to the argument will return
@racket[#f], which will cause the contract-monitoring system to blame
the client. Similarly, the server module must provide an exact,
nonnegative integer as the result of @racket[balance] to remain
blameless.
Of course it makes no sense to restrict a channel of
communication to values that the client doesn't
understand. Therefore the module also exports
the @scheme[amount?] predicate itself, with a contract
saying that it accepts an arbitrary value and returns a
boolean.
Of course, it makes no sense to restrict a channel of communication to
values that the client doesn't understand. Therefore the module also
exports the @racket[amount?] predicate itself, with a contract saying
that it accepts an arbitrary value and returns a boolean.
In this case, we could also have used @scheme[natural-number/c], which
is a contract defined in @schememodname[scheme/contract] that is
equivalent to @scheme[amount] (modulo the name):
@schememod[
scheme
In this case, we could also have used @racket[natural-number/c] in
place of @racket[amount?], since it implies exactly the same check:
@racketblock[
(provide/contract
(code:comment "an amount is a natural number of cents")
[deposit (-> natural-number/c any)])
(define this 0)
(define (deposit a) (set! this (+ this a)))
[deposit (-> natural-number/c any)]
[balance (-> natural-number/c)])
]
Lesson: learn about the built-in contracts in @schememodname[scheme/contract].
Every function that accepts one argument can be treated as a predicate
and thus used as a contract. For combining existing checks into a new
one, however, contract combinators such as @racket[and/c] and
@racket[or/c] are often useful. For example, here is yet another way
to write the contracts above:
@ctc-section[#:tag "and-or"]{The @scheme[and/c], @scheme[or/c], and @scheme[listof] Contract Combinators}
Both @scheme[and/c] and @scheme[or/c] combine contracts and
they do what you expect them to do.
For example, if we didn't have @scheme[natural-number/c], the
@scheme[amount?] contract is a bit opaque. Instead, we would define it
as follows:
@schememod[
scheme
(define amount
@racketblock[
(define amount/c
(and/c number? integer? exact? (or/c positive? zero?)))
(provide/contract
(code:comment "an amount is a natural number of cents")
(code:comment "is the given number an amount?")
[deposit (-> amount any)])
(define this 0)
(define (deposit a) (set! this (+ this a)))
[deposit (-> amount/c any)]
[balance (-> amount/c)])
]
That is, amount is a contract that enforces the following conditions: the
value satisfies @scheme[number?] and @scheme[integer?] and
@scheme[exact?] and is either @scheme[positive?] or
@scheme[zero?].
Other values also serve double duty as contracts. For example, if a
function accepts a number or @racket[#f], @racket[(or/c number? #f)]
suffices. Similarly, the @racket[amount/c] contract could have been
written with a @racket[0] in place of @racket[zero?]. If you use a
regular expression as a contract, the contract accepts strings and
byte strings that match the regular expression.
Oh, we almost forgot. What do you think @scheme[(listof char?)]
means? Hint: it is a contract!
Naturally, you can mix your own contract-implementing functions with
combinators like @racket[and/c]. Here is a module for creating strings
from banking records:
@ctc-section[#:tag "range"]{Restricting the Range of a Function}
Consider a utility module for creating strings from banking records:
@schememod[
scheme
@racketmod[
racket
(define (has-decimal? str)
(define L (string-length str))
@ -230,22 +241,19 @@ scheme
[format-nat (-> natural-number/c
(and/c string? has-decimal?))])
]
The contract of the exported function @scheme[format-number] specifies that
the function consumes a number and produces a string.
The contract of the exported function @scheme[format-nat] is more
interesting than the one of @scheme[format-number]. It consumes only
The contract of the exported function @racket[format-number] specifies
that the function consumes a number and produces a string. The
contract of the exported function @racket[format-nat] is more
interesting than the one of @racket[format-number]. It consumes only
natural numbers. Its range contract promises a string that has a
@litchar{.} in the third position from the right.
@(exercise) Strengthen the promise of the range contract for
@scheme[format-nat] so that it admits only strings with digits and a single
dot.
If we want to strengthen the promise of the range contract for
@racket[format-nat] so that it admits only strings with digits and a single
dot, we could write it like this:
@(solution)
@schememod[
scheme
@racketmod[
racket
(define (digit-char? x)
(member x '(#\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\0)))
@ -263,11 +271,10 @@ scheme
(andmap digit-char?
(string->list (substring str (- L 2) L)))))
....
(provide/contract
...
(code:comment "convert a number to a string")
[format-number (-> number? string?)]
....
(code:comment "convert an amount (natural number) of cents")
(code:comment "into a dollar based string")
[format-nat (-> natural-number/c
@ -275,23 +282,22 @@ scheme
is-decimal-string?))])
]
Alternately, in this case, we could use a regular expression as a
contract:
@ctc-section[#:tag "coercion"]{Contracts Coerced from Other Values}
@racketmod[
racket
The contract library treats a number of Scheme values as if they are
contracts directly. We've already seen one main use of that: predicates. Every
function that accepts one argument can be treated as a predicate
and thus used as a contract.
(provide/contract
....
(code:comment "convert an amount (natural number) of cents")
(code:comment "into a dollar based string")
[format-nat (-> natural-number/c
(and/c string?
#rx"[0-9]*\\.[0-9][0-9][0-9]"))])
]
But many other values also play double duty as contracts.
For example, if your function accepts a number or @scheme[#f],
@scheme[(or/c number? #f)] suffices. Similarly, the @scheme[result/c] contract
could have been written with a @scheme[0] in place of @scheme[zero?].
Even better, if you use a regular expression as a contract, the contract
accepts strings that match the regular expression. For example,
the @scheme[is-decimal-string?] predicate could have been written
@scheme[#rx"[0-9]*\\.[0-9][0-9][0-9]"].
@; ------------------------------------------------------------------------
@ctc-section{Contracts on Higher-order Functions}
@ -302,35 +308,84 @@ themselves, can be used as contracts on the arguments and
results of a function.
For example,
@schemeblock[(-> integer? (-> integer? integer?))]
is a contract that describes a curried function. It matches
functions that accept one argument and then return another
function accepting a second argument before finally
returning an integer.
This contract
@schemeblock[(-> (-> integer? integer?) integer?)]
describes functions that accept other functions as inputs.
@racketblock[(-> integer? (-> integer? integer?))]
@ctc-section{The Difference Between @scheme[any] and @scheme[any/c]}
is a contract that describes a curried function. It matches functions
that accept one argument and then return another function accepting a
second argument before finally returning an integer. If a server
exports a function @racket[make-adder] with this contract, and if
@racket[make-adder] returns a value other than a function, then the
server is to blame. If @racket[make-adder] does return a function, but
the resulting function is applied to a value other than an integer,
then the client is to blame.
The contract @scheme[any/c] accepts any value, and
@scheme[any] is a keyword that can appear in the range of
the function contracts (@scheme[->], @scheme[->*], and
@scheme[->d]), so it is natural to wonder what the
difference between these two contracts is:
@schemeblock[
(-> integer? any)
(-> integer? any/c)
Similarly, the contract
@racketblock[(-> (-> integer? integer?) integer?)]
describes functions that accept other functions as its input. If a
server exports a function @racket[twice] with this contract and the
@racket[twice] is applied to a value other than a function of one
argument, then the client is to blame. If @racket[twice] is applied to
a function of one argument and @racket[twice] calls the give function
on a value other than an integer, then the server is to blame.
@; ----------------------------------------------------------------------
@ctc-section[#:tag "flat-named-contracts"]{Contract Messages with ``???''}
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
piece of art:
@racketmod[
racket
(provide/contract
[deposit (-> (lambda (x)
(and (number? x) (integer? x) (>= x 0)))
any)])
(define this 0)
(define (deposit a) ...)
]
Both allow any result, right? There is one important difference:
in the first case, the function may return anything at
all, including multiple values. In the second case, the
function may return any value, but not more than one. For
example, this function:
@schemeblock[
(define (f x) (values (+ x 1) (- x 1)))
]
meets the first contract, but not the second one.}
Several clients used your module. Others used their
modules in turn. And all of a sudden one of them sees this error
message:
@inset-flow{@racketerror{bank-client broke the contract (-> ??? any)
it had with myaccount on deposit; expected <???>, given: -10}}
Clearly, @racket[bank-client] is a module that uses @racket[myaccount]
but what is the @racketerror{???} doing there? Wouldn't it be nice if
we had a name for this class of data much like we have string, number,
and so on?
For this situation, Racket provides @deftech{flat named
contracts}. The use of ``contract'' in this term shows that contracts
are first-class values. The ``flat'' means that the collection of data
is a subset of the built-in atomic classes of data; they are described
by a predicate that consumes all Racket values and produces a
boolean. The ``named'' part says what we want to do, which is to name
the contract so that error messages become intelligible:
@racketmod[
racket
(define (amount? x) (and (number? x) (integer? x) (>= x 0)))
(define amount (flat-named-contract 'amount amount?))
(provide/contract
[deposit (amount . -> . any)])
(define this 0)
(define (deposit a) ...)
]
With this little change, the error message becomes all of the
sudden quite readable:
@inset-flow{@racketerror{bank-client broke the contract (-> amount
any) it had with myaccount on deposit; expected <amount>, given: -10}}

View File

@ -3,12 +3,12 @@
scribble/eval
"guide-utils.ss"
"contracts-utils.ss"
(for-label scheme/contract))
(for-label racket/contract))
@title[#:tag "contracts-struct"]{Contracts on Structures}
Modules deal with structures in two ways. First they export
@scheme[struct] definitions, i.e., the ability to create
@racket[struct] definitions, i.e., the ability to create
structs of a certain kind, to access their fields, to modify
them, and to distinguish structs of this kind against every
other kind of value in the world. Second, on occasion a
@ -17,12 +17,14 @@ its fields contain values of a certain kind. This section
explains how to protect structs with contracts for both
uses.
@ctc-section[#:tag "single-struct"]{Promising Something About a Specific Structure}
@; ----------------------------------------------------------------------
@ctc-section[#:tag "single-struct"]{Guarantees for a Specific Value}
Yes. If your module defines a variable to be a structure, then on export you
can specify the structures shape:
@schememod[
scheme
If your module defines a variable to be a structure, then you can
specify the structure's shape using @racket[struct/c]:
@racketmod[
racket
(require lang/posn)
(define origin (make-posn 0 0))
@ -32,105 +34,98 @@ scheme
]
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 @racket[posn] structure. One of the @racket[posn]s it creates
and exports stands for the origin, i.e., @tt{(0,0)}, of the grid.
@ctc-section[#:tag "single-vector"]{Promising Something About a Specific Vector}
@margin-note{See also @racket[vector/c] and similar contract
combinators for (flat) compound data.}
Yes, again. See the help desk for information on @scheme[vector/c] and
similar contract combinators for (flat) compound data.
@; ----------------------------------------------------------------------
@ctc-section[#:tag "define-struct"]{Guarantees for All Values}
@ctc-section[#:tag "define-struct"]{Ensuring that All Structs are Well-Formed}
The book @|HtDP| teaches that @racket[posn]s should contain only
numbers in their two fields. With contracts we would enforce this
informal data definition as follows:
The book @link["http://www.htdp.org/"]{@italic{How to Design
Programs}} teaches that @scheme[posn]s should contain only
numbers in their two fields. With contracts we would enforce
this informal data definition as follows:
@schememod[
scheme
(define-struct posn (x y))
@racketmod[
racket
(struct posn (x y))
(provide/contract
[struct posn ((x number?) (y number?))]
[p-okay posn?]
[p-sick posn?])
(define p-okay (make-posn 10 20))
(define p-sick (make-posn 'a 'b))
(define p-okay (posn 10 20))
(define p-sick (posn 'a 'b))
]
This module exports the entire structure definition: @scheme[make-posn],
@scheme[posn?], @scheme[posn-x], @scheme[posn-y],
@scheme[set-posn-x!], and @scheme[set-posn-y!]. Each function enforces
or promises that the two fields of a @scheme[posn] structure are
numbers---when the values flow across the module boundary.
This module exports the entire structure definition: @racket[posn],
@racket[posn?], @racket[posn-x], @racket[posn-y],
@racket[set-posn-x!], and @racket[set-posn-y!]. Each function enforces
or promises that the two fields of a @racket[posn] structure are
numbers --- when the values flow across the module boundary. Thus, if
a client calls @racket[posn] on @racket[10] and @racket['a], the
contract system signals a contract violation.
Thus, if a client calls @scheme[make-posn] on @scheme[10] and
@scheme['a], the contract system signals a contract
violation.
The creation of @scheme[p-sick] inside of the @scheme[posn] module,
however, does not violate the contracts. The function @scheme[make-posn] is
used internally so @scheme['a] and @scheme['b] don't cross the module
boundary. Similarly, when @scheme[p-sick] crosses the boundary of
@scheme[posn], the contract promises a @scheme[posn?] and nothing
else. In particular, this check does @italic{not} require that the fields of
@scheme[p-sick] are numbers.
The creation of @racket[p-sick] inside of the @racket[posn] module,
however, does not violate the contracts. The function @racket[posn] is
used internally, so @racket['a] and @racket['b] don't cross the module
boundary. Similarly, when @racket[p-sick] crosses the boundary of
@racket[posn], the contract promises a @racket[posn?] and nothing
else. In particular, this check does @italic{not} require that the
fields of @racket[p-sick] are numbers.
The association of contract checking with module boundaries implies that
@scheme[p-okay] and @scheme[p-sick] look alike from a client's
@racket[p-okay] and @racket[p-sick] look alike from a client's
perspective until the client extracts the pieces:
@schememod[
scheme
@racketmod[
racket
(require lang/posn)
... (posn-x p-sick) ...
]
Using @scheme[posn-x] is the only way the client can find out what
a @scheme[posn] contains in the @scheme[x] field. The application of
@scheme[posn-x] sends @scheme[p-sick] back into the
@scheme[posn] module and the result value -- @scheme['a] here -- back to
Using @racket[posn-x] is the only way the client can find out what
a @racket[posn] contains in the @racket[x] field. The application of
@racket[posn-x] sends @racket[p-sick] back into the
@racket[posn] module and the result value -- @racket['a] here -- back to
the client, again across the module boundary. At this very point, the contract
system discovers that a promise is broken. Specifically, @scheme[posn-x]
system discovers that a promise is broken. Specifically, @racket[posn-x]
doesn't return a number but a symbol and is therefore blamed.
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
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]
error is located in the @racket[posn] module. The bad news is that the
explanation is misleading. Although it is true that @racket[posn-x]
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
created a @racket[posn] from symbols, i.e., the programmer who added
@schemeblock[
(define p-sick (make-posn 'a 'b))
@racketblock[
(define p-sick (posn 'a 'b))
]
to the module. So, when you are looking for bugs based on contract violations,
keep this example in mind.
@(exercise) Use your knowledge from the
@ctc-link["single-struct"] section on exporting specific
structs and change the contract for @scheme[p-sick] so that
the error is caught when @scheme[sick] is exported.
to the module. So, when you are looking for bugs based on contract
violations, keep this example in mind.
@(solution)
If we want to fix the contract for @racket[p-sick] so that the error
is caught when @racket[sick] is exported, a single change suffices:
A single change suffices:
@schemeblock[
@racketblock[
(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.
That is, instead of exporting @racket[p-sick] as a plain
@racket[posn?], we use a @racket[struct/c] contract to enforce
constraints on its components.
@; ----------------------------------------------------------------------
@ctc-section[#:tag "lazy-contracts"]{Checking Properties of Data Structures}
Contracts written using @scheme[struct/c] immediately
Contracts written using @racket[struct/c] immediately
check the fields of the data structure, but sometimes this
can have disastrous effects on the performance of a program
that does not, itself, inspect the entire data structure.
@ -144,12 +139,12 @@ subtree are smaller than the number in the node, and all of
the numbers in the right subtree are larger than the number
in the node.
We can implement a search function @scheme[in?] that takes
We can implement a search function @racket[in?] that takes
advantage of the structure of the binary search tree.
@schememod[
scheme
@racketmod[
racket
(define-struct node (val left right))
(struct node (val left right))
(code:comment "determines if `n' is in the binary search tree `b',")
(code:comment "exploiting the binary search tree invariant")
@ -180,48 +175,47 @@ scheme
]
In a full binary search tree, this means that
the @scheme[in?] function only has to explore a
the @racket[in?] function only has to explore a
logarithmic number of nodes.
The contract on @scheme[in?] guarantees that its input
The contract on @racket[in?] guarantees that its input
is a binary search tree. But a little careful thought
reveals that this contract defeats the purpose of the binary
search tree algorithm. In particular, consider the
inner @scheme[cond] in the @scheme[in?]
function. This is where the @scheme[in?] function gets
inner @racket[cond] in the @racket[in?]
function. This is where the @racket[in?] function gets
its speed: it avoids searching an entire subtree at each
recursive call. Now compare that to the @scheme[bst-between?]
function. In the case that it returns @scheme[#t], it
recursive call. Now compare that to the @racket[bst-between?]
function. In the case that it returns @racket[#t], it
traverses the entire tree, meaning that the speedup
of @scheme[in?] is lost.
of @racket[in?] is lost.
In order to fix that, we can employ a new strategy for
checking the binary search tree contract. In particular, if
we only checked the contract on the nodes
that @scheme[in?] looks at, we can still guarantee that
that @racket[in?] looks at, we can still guarantee that
the tree is at least partially well-formed, but without
changing the complexity.
To do that, we need to
use @scheme[define-contract-struct] in place
of @scheme[define-struct]. Like @scheme[define-struct],
@scheme[define-contract-struct] defines a maker,
predicate, and selectors for a new
structure. Unlike @scheme[define-struct], it also
defines contract combinators, in this
case @scheme[node/c] and @scheme[node/dc]. Also unlike
@scheme[define-struct], it does not allow mutators, making
its structs always immutable.
To do that, we need to use @racket[define-contract-struct] in place of
@racket[struct]. Like @racket[struct] (and more like
@racket[define-struct]), @racket[define-contract-struct] defines a
maker, predicate, and selectors for a new structure. Unlike
@racket[define-struct], it also defines contract combinators, in this
case @racket[node/c] and @racket[node/dc]. Also unlike
@racket[define-struct], it does not allow mutators, making its structs
always immutable.
The @scheme[node/c] function accepts a contract for each
The @racket[node/c] function accepts a contract for each
field of the struct and returns a contract on the
struct. More interestingly, the syntactic
form @scheme[node/dc] allows us to write dependent
form @racket[node/dc] allows us to write dependent
contracts, i.e., contracts where some of the contracts on
the fields depend on the values of other fields. We can use
this to define the binary search tree contract:
@schememod[
scheme
@racketmod[
racket
(define-contract-struct node (val left right))
@ -245,30 +239,30 @@ scheme
[in? (number? bst/c . -> . boolean?)])
]
In general, each use of @scheme[node/dc] must name the
In general, each use of @racket[node/dc] must name the
fields and then specify contracts for each field. In the
above, the @scheme[val] field is a contract that accepts
values between @scheme[low] and @scheme[high].
The @scheme[left] and @scheme[right] fields are
dependent on the value of the @scheme[val] field,
above, the @racket[val] field is a contract that accepts
values between @racket[low] and @racket[high].
The @racket[left] and @racket[right] fields are
dependent on the value of the @racket[val] field,
indicated by their second sub-expressions. Their contracts
are built by recursive calls to
the @scheme[bst-between/c] function. Taken together,
the @racket[bst-between/c] function. Taken together,
this contract ensures the same thing that
the @scheme[bst-between?] function checked in the
the @racket[bst-between?] function checked in the
original example, but here the checking only happens
as @scheme[in?] explores the tree.
as @racket[in?] explores the tree.
Although this contract improves the performance
of @scheme[in?], restoring it to the logarithmic
of @racket[in?], restoring it to the logarithmic
behavior that the contract-less version had, it is still
imposes a fairly large constant overhead. So, the contract
library also provides @scheme[define-opt/c] that brings
library also provides @racket[define-opt/c] that brings
down that constant factor by optimizing its body. Its shape
is just like the @scheme[define] above. It expects its
is just like the @racket[define] above. It expects its
body to be a contract and then optimizes that contract.
@schemeblock[
@racketblock[
(define-opt/c (bst-between/c low high)
(or/c null?
(node/dc [val (between/c low high)]

View File

@ -5,7 +5,7 @@
@title[#:tag "contracts" #:style 'toc]{Contracts}
This chapter provides a gentle introduction to PLT Scheme's
This chapter provides a gentle introduction to Racket's
contract system.
@refdetails["contracts"]{contracts}

View File

@ -28,7 +28,7 @@ A @racket[lambda] form with @math{n} @racket[_arg-id]s accepts
]
@;------------------------------------------------------------------------
@section{Declaring a Rest Argument}
@section[#:tag "rest-args"]{Declaring a Rest Argument}
A @racket[lambda] expression can also have the form
@ -207,7 +207,7 @@ remaining by-position arguments.
@refdetails["lambda"]{function expressions}
@;------------------------------------------------------------------------
@section{Arity-Sensitive Functions: @racket[case-lambda]}
@section[#:tag "case-lambda"]{Arity-Sensitive Functions: @racket[case-lambda]}
The @racket[case-lambda] form creates a function that can have
completely different behaviors depending on the number of arguments

View File

@ -28,9 +28,9 @@ evaluator is created using @scheme[make-base-eval]. See also
Uses of @scheme[code:comment] and @schemeidfont{code:blank} are
stipped from each @scheme[datum] before evaluation.
If a @scheme[datum] has the form @scheme[(eval:alts #,(svar
show-datum) #,(svar eval-datum))], then @svar[show-datum] is typeset,
while @svar[eval-datum] is evaluated.}
If a @scheme[datum] has the form @scheme[(@#,indexed-scheme[eval:alts]
#,(svar show-datum) #,(svar eval-datum))], then @svar[show-datum] is
typeset, while @svar[eval-datum] is evaluated.}
@defform*[[(interaction-eval datum)