racket/collects/scribblings/guide/contracts-general-function.scrbl
Asumu Takikawa 02219bda91 Fix guide section 7.3.8
Closes PR 13033
2012-08-16 16:02:43 -04:00

633 lines
23 KiB
Racket

#lang scribble/doc
@(require scribble/manual scribble/eval "guide-utils.rkt" "contracts-utils.rkt"
(for-label framework/framework racket/contract racket/gui))
@title[#:tag "contracts-general-functions"]{Contracts on Functions in General}
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, notably @racket[->*] and
@racket[->i].
@ctc-section[#:tag "optional"]{Optional Arguments}
Take a look at this excerpt from a string-processing module, inspired by the
@link["http://schemecookbook.org"]{Scheme cookbook}:
@racketmod[
racket
(provide
(contract-out
(code:comment @#,t{pad the given str left and right with})
(code:comment @#,t{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 @racket[string-pad-center], a function
that creates a string of a given @racket[width] with the
given string in the center. The default fill character is
@racket[#\space]; if the client module wishes to use a
different character, it may call @racket[string-pad-center]
with a third argument, a @racket[char], overwriting the
default.
The function definition uses optional arguments, which is
appropriate for this kind of functionality. The interesting
point here is the formulation of the contract for the
@racket[string-pad-center].
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
@racket[natural-number/c]. }
@item{The second one is a parenthesized group of contracts for all optional
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. 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}
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:
@racketblock[
(provide
(contract-out
[max-abs (->* (real?) () #:rest (listof real?) real?)]))
]
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}
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.}
@racketmod[
racket/gui
(define (ask-yes-or-no-question question
#:default answer
#:title title
#:width w
#:height h)
(define d (new dialog% [label title] [width w] [height h]))
(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 answer '(border) '())]))
(define no-b (new button%
[label "No"] [parent d]
[callback (λ (x y) (no))]
[style (if answer '() '(border))]))
(send d show #t)
answer)
(provide (contract-out
[ask-yes-or-no-question
(-> string?
#:default boolean?
#:title string?
#:width exact-integer?
#:height exact-integer?
boolean?)]))
]
@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].
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:
@racketblock[
(define (ask-yes-or-no-question question
#:default answer
#:title [title "Yes or No?"]
#:width [w 400]
#:height [h 200])
...)
]
To specify this function's contract, we need to use
@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-out
[ask-yes-or-no-question
(->* (string?
#:default boolean?)
(#:title string?
#:width exact-integer?
#:height exact-integer?)
boolean?)]))
]
That is, we put the mandatory keywords in the first section, and we
put the optional ones in the second section.
@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
of 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 such a function is formed with the @racket[case->]
combinator, which combines as many functional contracts as needed:
@racketblock[
(provide (contract-out
[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.
@;{
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-out
[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-out
[real-sqrt (->i ([argument (>=/c 1)])
[result (argument) (<=/c argument)])]))
]
@margin-note{The word ``indy'' is meant to suggest that blame may be
assigned to the contract itself, because the contract must be considered an
independent component. The name was chosen in
response to two existing labels---``lax'' and ``picky''---for different
semantics of function contracts in the research literature.}
The contract for the exported function @racket[real-sqrt] uses the
@racket[->i] rather than @racket[->*] function contract. The ``i''
stands for an @italic{indy dependent} contract, meaning the contract for the
function range depends on the value of the argument. The appearance
of @racket[argument] in the line for @racket[result]'s contract means
that the result depends on 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.
}
Going back to the bank-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 an
@racket[account] structure type and the following functions:
@racketblock[
(provide (contract-out
[balance (-> account? amount/c)]
[withdraw (-> account? amount/c account?)]
[deposit (-> account? amount/c account?)]))
]
Besides requiring that a client provide a valid amount for a
withdrawal, however, the amount should be less than or equal to 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")
(struct account (balance))
(define amount/c natural-number/c)
(code:comment "section 2: the exports")
(provide
(contract-out
[create (amount/c . -> . account?)]
[balance (account? . -> . amount/c)]
[withdraw (->i ([acc account?]
[amt (acc) (and/c amount/c (<=/c (balance acc)))])
[result (acc amt)
(and/c account?
(lambda (res)
(>= (balance res)
(- (balance acc) amt))))])]
[deposit (->i ([acc account?]
[amt amount/c])
[result (acc amt)
(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[->i] 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")
(define (mk-account-contract acc amt op msg)
(define balance0 (balance acc))
(define (ctr a)
(and (account? a) (op balance0 (balance a))))
(flat-named-contract (format msg balance0) ctr))
(code:comment "section 2: the exports")
(provide
(contract-out
[create (amount/c . -> . account?)]
[balance (account? . -> . amount/c)]
[withdraw (->i ([acc account?]
[amt (acc) (and/c amount/c (<=/c (balance acc)))])
[result (acc amt) (mk-account-contract acc amt >= msg>)])]
[deposit (->i ([acc account?]
[amt amount/c])
[result (acc amt)
(mk-account-contract acc amt <= msg<)])]))
(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)))
]
@ctc-section[#:tag "arrow-d-eval-order"]{Checking State Changes}
The @racket[->i] contract combinator can also ensure that a
function only modifies state according to certain
constraints. For example, consider this contract
(it is a slightly simplified from the function
@racket[preferences:add-panel] in the framework):
@racketblock[
(->i ([parent (is-a?/c area-container-window<%>)])
[_ (parent)
(let ([old-children (send parent get-children)])
(λ (child)
(andmap eq?
(append old-children (list child))
(send parent get-children))))])
]
It says that the function accepts a single argument, named
@racket[parent], and that @racket[parent] must be
an object matching the interface @racket[area-container-window<%>].
The range contract ensures that the function only modifies
the children of @racket[parent] by adding a new child to the
front of the list. It accomplishes this by using the
@racket[_] instead of a normal identifier, which tells the
contract library that the range contract does not depend on
the values of any of the results, and thus the contract
library evaluates the expression following the @racket[_]
when the function is called, instead of when it
returns. Therefore the call to the @racket[get-children] method
happens before the function under the contract is called.
When the function under contract returns, its result is
passed in as @racket[child], and the contract ensures that
the children after the function return are the same as the
children before the function called, but with one more
child, at the front of the list.
To see the difference in a toy example that focuses
on this point, consider this program
@racketmod[
racket
(define x '())
(define (get-x) x)
(define (f) (set! x (cons 'f x)))
(provide
(contract-out
[f (->i () [_ (begin (set! x (cons 'ctc x)) any/c)])]
[get-x (-> (listof symbol?))]))
]
If you were to require this module, call @racket[f], then
the result of @racket[get-x] would be @racket['(f ctc)]. In
contrast, if the contract for @racket[f] were
@racketblock[(->i () [res (begin (set! x (cons 'ctc x)) any/c)])]
(only changing the underscore to @racket[res]), then
the result of @racket[get-x] would be @racket['(ctc f)].
@ctc-section[#:tag "multiple"]{Multiple Result Values}
The function @racket[split] consumes a list of @racket[char]s
and delivers the string that occurs before the first occurrence of
@racket[#\newline] (if any) and the rest of the list:
@racketblock[
(define (split l)
(define (split l w)
(cond
[(null? l) (values (list->string (reverse w)) '())]
[(char=? #\newline (car l))
(values (list->string (reverse w)) (cdr l))]
[else (split (cdr l) (cons (car l) w))]))
(split l '()))
]
It is a typical multiple-value function, returning two values by
traversing a single list.
The contract for such a function can use the ordinary
function arrow @racket[->], since @racket[->]
treats @racket[values] specially when it appears as the
last result:
@racketblock[
(provide (contract-out
[split (-> (listof char?)
(values string? (listof char?)))]))
]
The contract for such a function can also be written
using @racket[->*]:
@racketblock[
(provide (contract-out
[split (->* ((listof char?))
()
(values string? (listof char?)))]))
]
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 optional arguments. The contracts for the
results are inside @racket[values]: a string and a list of
characters.
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[->i] contract combinator:
@racketblock[
(define (substring-of? s)
(flat-named-contract
(format "substring of ~s" s)
(lambda (s2)
(and (string? s2)
(<= (string-length s2) (string-length s))
(equal? (substring s 0 (string-length s2)) s2)))))
(provide
(contract-out
[split (->i ([fl (listof char?)])
(values [s (fl) (substring-of? (list->string fl))]
[c (listof char?)]))]))
]
Like @racket[->*], the @racket[->i] combinator uses a function over the
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
value. In this case, the second contract is the same as before, ensuring
that the second result is a list of @racket[char]s. In contrast, the
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
cheaper version:
@racketblock[
(provide
(contract-out
[split (->i ([fl (listof char?)])
(values [s (fl) (string-len/c (length fl))]
[c (listof char?)]))]))
]
@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
latter. Unless the arity of the given function matches the length of the
given list, your procedure is in trouble.
Consider this @racket[n-step] function:
@racketblock[
(code:comment "(number ... -> (union #f number?)) (listof number) -> void")
(define (n-step proc inits)
(let ([inc (apply proc inits)])
(when inc
(n-step proc (map (λ (x) (+ x inc)) inits)))))
]
The argument of @racket[n-step] is @racket[proc], a function
@racket[proc] whose results are either numbers or false, and a list. It
then applies @racket[proc] to the list @racket[inits]. As long as
@racket[proc] returns a number, @racket[n-step] treats that number
as an increment for each of the numbers in @racket[inits] and
recurs. When @racket[proc] returns @racket[false], the loop stops.
Here are two uses:
@racketblock[
(code:comment "nat -> nat")
(define (f x)
(printf "~s\n" x)
(if (= x 0) #f -1))
(n-step f '(2))
(code:comment "nat nat -> nat")
(define (g x y)
(define z (+ x y))
(printf "~s\n" (list x y z))
(if (= z 0) #f -1))
(n-step g '(1 1))
]
A contract for @racket[n-step] must specify two aspects of
@racket[proc]'s behavior: its arity must include the number of elements
in @racket[inits], and it must return either a number or
@racket[#f]. The latter is easy, the former is difficult. At first
glance, this appears to suggest a contract that assigns a
@italic{variable-arity} to @racket[proc]:
@racketblock[
(->* ()
(listof any/c)
(or/c number? false/c))
]
This contract, however, says that the function must accept @emph{any}
number of arguments, not a @emph{specific} but
@emph{undetermined} number. Thus, applying @racket[n-step] to
@racket[(lambda (x) x)] and @racket[(list 1)] breaks the contract
because the given function accepts only one argument.
The correct contract uses the @racket[unconstrained-domain->]
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
specify the correct @racket[n-step]'s contract:
@racketblock[
(provide
(contract-out
[n-step
(->i ([proc (inits)
(and/c (unconstrained-domain->
(or/c false/c number?))
(λ (f) (procedure-arity-includes?
f
(length inits))))]
[inits (listof number?)])
()
any)]))
]