7.3 Contracts on Functions in General

► |  | Guide: Racket

1 | Welcome to Racket
2 | Racket Essentials
3 | Built-In Datatypes
4 | Expressions and Definitions
5 | Programmer-Defined Datatypes
6 | Modules
7 | Contracts
8 | Input and Output
9 | Regular Expressions
10 | Exceptions and Control
11 | Iterations and Comprehensions
12 | Pattern Matching
13 | Classes and Objects
14 | Units (Components)
15 | Reflection and Dynamic Evaluation
16 | Macros
17 | Creating Languages
18 | Performance
19 | Running and Creating Executables
20 | Compilation and Configuration
21 | More Libraries
22 | Dialects of Racket and Scheme
| Bibliography
| Index

▼ | 7 | Contracts

7.1 | Contracts and Boundaries
7.2 | Simple Contracts on Functions
7.3 | Contracts on Functions in General
7.4 | Contracts on Structures
7.5 | Abstract Contracts using #: exists and #: ∃
7.6 | Additional Examples
7.7 | Gotchas

► | 7.3 | Contracts on Functions in General

7.3.1 | Optional Arguments
7.3.2 | Rest Arguments
7.3.3 | Keyword Arguments
7.3.4 | Optional Keyword Arguments
7.3.5 | Contracts for case-lambda
7.3.6 | Argument and Result Dependencies
7.3.7 | Checking State Changes
7.3.8 | Multiple Result Values
7.3.9 | Fixed but Statically Unknown Arities

On this page:

7.3.1`` Optional Arguments
7.3.2`` Rest Arguments
7.3.3`` Keyword Arguments
7.3.4`` Optional Keyword Arguments
7.3.5`` Contracts for case-lambda
7.3.6`` Argument and Result Dependencies
7.3.7`` Checking State Changes
7.3.8`` Multiple Result Values
7.3.9`` Fixed but Statically Unknown Arities

top← prev up next →

#### 7.3`` Contracts on Functions in General

The -> 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 ->* and ->d.

##### 7.3.1`` Optional Arguments

Take a look at this excerpt from a string-processing module, inspired by the Scheme cookbook:

#lang racket
( provide/contract
; pad the given str left and right with
; the (optional) char so that it is centered
[string-pad-center (->* ( string? natural-number/c)
( char?)
string?)])
( define (string-pad-center str width [pad #\space])
( define field-width ( min width ( string-length str)))
( define rmargin ( ceiling (/ (- width field-width) 2)))
( define lmargin ( floor (/ (- width field-width) 2)))
( string-append ( build-string lmargin ( λ (x) pad))
str
( build-string rmargin ( λ (x) pad))))

The module exports string-pad-center, a function that creates a string of a given width with the given string in the center. The default fill character is #\space; if the client module wishes to use a different character, it may call string-pad-center with a third argument, a 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 string-pad-center.

The contract combinator ->*, demands several groups of contracts:

- The first one is a parenthesized group of contracts for all required arguments. In this example, we see two: string? and natural-number/c.

- The second one is a parenthesized group of contracts for all optional arguments: char?.

- 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.

##### 7.3.2`` Rest Arguments

The 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 max-abs:

> > > See Declaring a Rest Argument for an introduction to rest arguments.

( 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 ->*: a #:rest keyword specifies a contract on a list of arguments after the required and optional arguments:

( provide/contract
[max-abs (->* ( real?) () #:rest ( listof real?) real?)])

As always for ->*, 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 #:rest; since all additional arguments must be real numbers, the list of rest arguments must satisfy the contract ( listof real?).

##### 7.3.3`` Keyword Arguments

It turns out that the -> 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:

> > > See Declaring Keyword Arguments for an introduction to keyword arguments.

#lang 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
[ask-yes-or-no-question
(-> string?
#:default boolean?
#:title string?
#:width exact-integer?
#:height exact-integer?
boolean?)])

> > > If you really want to ask a yes-or-no question via a GUI, you should use message-box/custom. For that matter, it’s usually better to provide buttons with more specific answers than “yes” and “no.”

The contract for ask-yes-or-no-question uses ->, and in the same way that lambda (or define-based functions) allows a keyword to precede a functions formal argument, -> allows a keyword to precede a function contract’s argument contract. In this case, the contract says that ask-yes-or-no-question must receive four keyword arguments, one for each of the keywords #:default, #:title, #:width, and #:height. As in a function definition, the order of the keywords in -> relative to each other does not matter for clients of the function; only the relative order of argument contracts without keywords matters.

##### 7.3.4`` Optional Keyword Arguments

Of course, many of the parameters in ask-yes-or-no-question (from the previous question) have reasonable defaults and should be made optional:

( 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 ->* 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 #:default and optional keywords #:title, #:width, and #:height. So, we write the contract like this:

( provide/contract
[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.

##### 7.3.5`` Contracts for case-lambda

A function defined with case-lambda might impose different constraints on its arguments depending on how many are provided. For example, a report-cost function might convert either a pair or numbers or a string into a new string:

> > > See Arity-Sensitive Functions: case-lambda for an introduction to case-lambda.

( define report-cost
( case-lambda
[(lo hi) ( format "between $~a and $~a" lo hi)]
[(desc) ( format "~a of dollars" desc)]))

> (report-cost 5 8)
"between $5 and $8"
> (report-cost "millions")
"millions of dollars"

The contract for such a function is formed with the case-> combinator, which combines as many functional contracts as needed:

( provide/contract
[report-cost
( case->
( integer? integer? . -> . string?)
( string? . -> . string?))])

As you can see, the contract for report-cost combines two function contracts, which is just as many clauses as the explanation of its functionality required.

##### 7.3.6`` Argument and Result Dependencies

The following is an excerpt from an imaginary numerics module:

( provide/contract
[real-sqrt (->d ([argument (>=/c 1)])
()
[result (<=/c argument)])])

The contract for the exported function real-sqrt uses the ->d rather than ->* function contract. The “d” stands for a dependent contract, meaning the contract for the function range depends on the value of the argument. In this particular case, the argument of 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 ->* contract, but with names added that can be used elsewhere in the contract.

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 account structure type and the following functions:

( provide/contract
[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 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:

#lang racket
; section 1: the contract definitions
( struct account (balance))
( define amount/c natural-number/c)
; 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))))])])
; 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 create and balance. For withdraw and deposit, however, the contracts check and guarantee the more complicated constraints on balance and deposit. The contract on the second argument to withdraw uses (balance acc) to check whether the supplied withdrawal amount is small enough, where acc is the name given within ->d to the function’s first argument. The contract on the result of withdraw uses both acc and amt to guarantee that no more than that requested amount was withdrawn. The contract on deposit similarly uses acc and 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 flat-named-contract within a helper function mk-account-contract to provide better error messages.

#lang racket
; 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))
; 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 (mk-account-contract acc amt >= msg>)])]
[deposit (->d ([acc account?]
[amt amount/c])
()
[result (mk-account-contract acc amt <= msg<)])])
; 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)))

##### 7.3.7`` Checking State Changes

The ->d 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 preferences:add-panel in the framework):

(->d ([parent ( is-a?/c area-container-window<%>)])
()
[_
( 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 parent, and that parent must be an object matching the interface area-container-window<%>.

The range contract ensures that the function only modifies the children of parent by adding a new child to the front of the list. It accomplishes this by using the _ 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 _ when the function is called, instead of when it returns. Therefore the call to the get-children method happens before the function under the contract is called. When the function under contract returns, its result is passed in as 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

#lang racket
( define x '())
( define (get-x) x)
( define (f) ( set! x ( cons 'f x)))
( provide/contract
[f (->d () () [_ ( begin ( set! x ( cons 'ctc x)) any/c)])]
[get-x (-> ( listof symbol?))])

If you were to require this module, call f, then the result of get-x would be '(f ctc). In contrast, if the contract for f were
(->d () () [res ( begin ( set! x ( cons 'ctc x)) any/c)])
(only changing the underscore to res), then the result of get-x would be '(ctc f).

##### 7.3.8`` Multiple Result Values

The function split consumes a list of chars and delivers the string that occurs before the first occurrence of #\newline (if any) and the rest of the list:

( 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 ->, since -> treats values specially when it appears as the last result:

( provide/contract
[split (-> ( listof char?)
( values string? ( listof char?)))])

The contract for such a function can also be written using ->*:

( provide/contract
[split (->* (( listof char?))
()
( values string? ( listof char?)))])

As before, the contract for the argument with ->* 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 values: a string and a list of characters.

Now, suppose that we also want to ensure that the first result of split is a prefix of the given word in list format. In that case, we need to use the ->d contract combinator:

( define (substring-of? s)
( flat-named-contract
( format "substring of ~s" s)
( lambda (s2)
( and ( string? s2)
(<= ( string-length s2) s)
( equal? ( substring s 0 ( string-length s2)) s2)))))
( provide/contract
[split (->d ([fl ( listof char?)])
()
( values [s (substring-of ( list->string fl))]
[c ( listof char?)]))])

Like ->*, the ->d 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 chars. 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:

( provide/contract
[split (->d ([fl ( listof char?)])
()
( values [s ( string-len/c ( length fl))]
[c ( listof char?)]))])

##### 7.3.9`` 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 n-step function:

; (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 n-step is proc, a function proc whose results are either numbers or false, and a list. It then applies proc to the list inits. As long as proc returns a number, n-step treats that number as an increment for each of the numbers in inits and recurs. When proc returns false, the loop stops.

Here are two uses:

; nat -> nat
( define (f x)
( printf "~s\n" x)
( if (= x 0) #f -1))
(n-step f '(2))
; 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 n-step must specify two aspects of proc’s behavior: its arity must include the number of elements in inits, and it must return either a number or #f. The latter is easy, the former is difficult. At first glance, this appears to suggest a contract that assigns a variable-arity to proc:

(->* ()
( listof any/c)
( or/c number? false/c))

This contract, however, says that the function must accept any number of arguments, not a specific but undetermined number. Thus, applying n-step to ( lambda (x) x) and ( list 1) breaks the contract because the given function accepts only one argument.

The correct contract uses the 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 n-step’s contract:

( provide/contract
[n-step
(->d ([proc
( and/c ( unconstrained-domain->
( or/c false/c number?))
( λ (f) ( procedure-arity-includes?
f
( length inits))))]
[inits ( listof number?)])
()
any)])

top← prev up next →