7.1 Contracts and Boundaries
Like a contract between two business partners, a software contract is an agreement between two parties. The agreement specifies obligations and guarantees for each “product” (or value) that is handed from one party to the other.
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.
promises to all clients of the above module that the value of amount will always be a positive number. The contract system monitors the module’s obligation carefully. Every time a client refers to amount, the monitor checks that the value of amount is indeed a positive number.
The contracts library is built into the Racket language, but if you wish to use racket/base, you can explicitly require the contracts library like this:
#lang racket/base (require racket/contract) ; now we can write contracts (provide (contract-out [amount positive?])) (define amount ...)
7.1.1 Contract Violations
If we bind amount to a number that is not positive,
#lang racket (provide (contract-out [amount positive?])) (define amount 0)
then, when the module is required, the monitoring system signals a violation of the contract and blames the module for breaking its promises.
An even bigger mistake would be to bind amount to a non-number value:
#lang racket (provide (contract-out [amount positive?])) (define amount 'amount)
In this case, the monitoring system will apply positive? to a symbol, but positive? reports an error, because its domain is only numbers. To 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 and/c:
(provide (contract-out [amount (and/c number? positive?)]))
7.1.2 Experimenting with Contracts and Modules
All of the contracts and modules in this chapter (excluding those just following) are written using the standard #lang syntax for describing modules. Since modules serve as the boundary between parties in a contract, examples involve multiple modules.
To experiment with multiple modules within a single module or within DrRacket’s definitions area, use Racket’s submodules. For example, try the example earlier in this section like this:
#lang racket (module+ server (provide (contract-out [amount (and/c number? positive?)])) (define amount 150)) (module+ main (require (submod ".." server)) (+ amount 10))
Each of the modules and their contracts are wrapped in parentheses with the module+ keyword at the front. The first form after module is the name of the module to be used in a subsequent require statement (where each reference through a require prefixes the name with "..").
7.1.3 Experimenting with Nested Contract Boundaries
In many cases, it makes sense to attach contracts at module boundaries. It is often convenient, however, to be able to use contracts at a finer granularity than modules. The define/contract form enables this kind of use:
#lang racket (define/contract amount (and/c number? positive?) 150) (+ amount 10)
In this example, the define/contract form establishes a contract boundary between the definition of amount and its surrounding context. In other words, the two parties here are the definition and the module that contains it.
Forms that create these nested contract boundaries can sometimes be subtle to use because they may have unexpected performance implications or blame a party that may seem unintuitive. These subtleties are explained in Using define/contract and -> and Contract boundaries and define/contract.