1 Welcome to PLT Scheme
2 Scheme 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 Performance
18 Running and Creating Executables
19 Compilation and Configuration
20 More Libraries
Bibliography
Index
On this page:
7.2.1 Restricting the Arguments of a Function
7.2.2 Arrows
7.2.3 Infix Contract Notation
7.2.4 Rolling Your Own Contracts for Function Arguments
7.2.5 The and/ c, or/ c, and listof Contract Combinators
7.2.6 Restricting the Range of a Function
7.2.7 The Difference Between any and any/ c
Version: 4.0.2

 

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

It is important to keep this picture in mind when you read the explanations of the various ways of imposing contracts on functions.

7.2.1 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:

  #lang 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:

If a “client” module were to apply deposit to '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.

Note: Instead of any you could also use the more specific contract void?, which says that the function will always return the (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, any tells the monitoring system not to check the return value. Additionally, it tells a potential client that the “server” module makes no promises at all about the function’s return value.

7.2.2 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:

  f : A -> B

Here the A and B are sets; A is the domain and 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 create or 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 -> to say "hey, expect this to be a function."

So -> 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,

  (provide/contract

   [create (-> string? number? boolean? account?)])

says that create is a function of three arguments: a string, a number, and a boolean. Its result is an account.

In short, the arrow -> is a contract combinator. Its purpose is to combine other contracts into a contract that says "this is a function and its arguments and its result are like that."

7.2.3 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 How to Design Programs, you have seen this many times. Indeed, you may have seen contracts such as these in other people’s code:

  (provide/contract

    [create (string? number? boolean? . -> . account?)])

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,

  (string? number? boolean? . -> . account?)

is really just a short-hand for

  (-> string? number? boolean? account?)

Of course, placing the arrow to the left of the range follows not only mathematical tradition but also that of typed functional languages.

7.2.4 Rolling Your Own Contracts for Function Arguments

The deposit function adds the given number to the value of 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.

To this end, the contract system allows programmers to define their own contracts:

  #lang scheme

  

  (define (amount? a)

    (and (number? a) (integer? a) (exact? a) (>= a 0)))

  

  (provide/contract

    ; an amount is a natural number of cents

    ; is the given number an amount?

    [deposit (-> amount? any)]

    [amount? (-> any/c boolean?)])

  

  (define this 0)

  (define (deposit a) (set! this (+ this a)))

The module introduces a predicate, amount?. The provide clause refers to this predicate, as a contract, for its specification of the contract of deposit.

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 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 natural-number/c, which is a contract defined in scheme/contract that is equivalent to amount (modulo the name):

  #lang scheme

  

  (provide/contract

    ; an amount is a natural number of cents

    [deposit (-> natural-number/c any)])

  

  (define this 0)

  (define (deposit a) (set! this (+ this a)))

Lesson: learn about the built-in contracts in scheme/contract.

7.2.5 The and/c, or/c, and listof Contract Combinators

Both and/c and or/c ombine contracts and they do what you expect them to do.

For example, if we didn’t have natural-number/c, the amount? contract is a bit opaque. Instead, we would define it as follows:

  #lang scheme

  

  (define amount

    (and/c number? integer? exact? (or/c positive? zero?)))

  

  (provide/contract

    ; an amount is a natural number of cents

    ; is the given number an amount?

    [deposit (-> amount any)])

  

  (define this 0)

  (define (deposit a) (set! this (+ this a)))

That is, amount is a contract that enforces the following conditions: the value satisfies number? and integer? and exact? and is either positive? or zero?.

Oh, we almost forgot. What do you think (listof char?) means? Hint: it is a contract!

7.2.6 Restricting the Range of a Function

Consider a utility module for creating strings from banking records:

  #lang scheme

  

  (define (has-decimal? str)

    (define L (string-length str))

    (and (>= L 3)

         (char=?

          #\.

          (string-ref result (- L 3)))))

  

  (provide/contract

    ; convert a random number to a string

    [format-number (-> number? string?)]

  

    ; convert an amount into a dollar based string

    [format-nat (-> natural-number/c

                    (and/c string? has-decimal?))])

The contract of the exported function format-number specifies that the function consumes a number and produces a string.

The contract of the exported function format-nat is more interesting than the one of format-number. It consumes only natural numbers. Its range contract promises a string that has a . in the third position from the right.

Exercise 2 Strengthen the promise of the range contract for format-nat so that it admits only strings with digits and a single dot.

Solution to exercise 2

  #lang scheme

  

  (define (digit-char? x)

    (member x '(#\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\0)))

  

  (define (has-decimal? str)

    (define L (string-length str))

    (and (>= L 3)

         (char=?

          #\.

          (string-ref result (- L 3)))))

  

  (define (is-decimal-string? str)

    (define L (string-length str))

    (and (has-decimal? str)

         (andmap digit-char?

                 (string->list (substring result 0 (- L 3))))

         (andmap digit-char?

                 (string->list (substring result (- L 2) L)))))

  

  (provide/contract

    ...

    ; convert a random number to a string

    [format-number (-> number? string?)]

  

    ; convert an amount (natural number) of cents

    ; into a dollar based string

    [format-nat (-> natural-number/c

                    (lambda (result)

                      (and (string? result)

                           (is-decimal-string? result))))])

7.2.7 The Difference Between any and any/c

The contract any/c accepts any value, and any is a keyword that can appear in the range of the function contracts (->, ->*, and ->d), so it is natural to wonder what the difference between these two contracts is:

  (-> integer? any)

  (-> integer? any/c)

Both allow any result, right? There are two differences: