Racket provides the racket/class library, which allows you to program in an object-oriented style with classes, mixins, and traits. Naturally, we would like to have the full power of contracts available for object-oriented programming as well. To that end, racket/class provides class contract combinators that work well for protecting classes and mixins.
The racket/class library also comes with Java-style interfaces. Sometimes, you want to build in contract checking for all classes that implement some particular interface instead of designing your contracts class-by-class. In Racket 5.3 and on, you can do this with our new interface contracts.
The rest of the article provides a short introduction to class and interface contracts.
(require slideshow/pict). We can define a simple fish class:
Our fish% objects will be able to eat some edible things and grow in size. Of course, we need some edible things:
> (define fish% (class object% (super-new) (init-field [weight 1] [color "sky blue"]) ; eat the given food object (define/public (eat edible) (define nutrition (send edible eaten)) (grow nutrition)) ; gain weight according to nutrition (define/private (grow nutrition) (set! weight (+ nutrition weight))) ; produce a pict of this fish (define/public (draw) (standard-fish 80 (* weight 40) #:color color))))
> (send (new fish%) draw)
Now a fish% can eat plankton% objects:
> (define edible<%> (interface () eaten))
> (define plankton% (class* object% (edible<%>) (super-new) ; not very nutritious... (define nutrition 0.2) (define/public (eaten) nutrition)))
After eating, Dory weighs more. Unfortunately, as we’ve written our classes so far we could deny our fish its dinner by providing an object that can’t be eaten:
> (define dory (new fish%))
> (send dory eat (new plankton%))
> (send dory draw)
This is where contracts can help out. Racket’s class contracts let you specify contracts on classes outside of the class hierarchy, so that you can have checked fish and unchecked fish, or fish that have different kinds of contracts.
> (define tire% ; don't eat this (class object% (super-new)))
> (send dory eat (new tire%)) send: no such method method name: eaten class name: tire%
For example, we can ensure that checked-fish% only eat edible things:
Now we get a contract error for providing something that’s not food. The contract error comes with blame, which allows us to pinpoint the code that caused the violation, in this case the application of the eat method. Of course, we can also contract Dory so that she doesn’t have to fast again:
> (define fish/c (class/c (init-field [weight real?]) [eat (->m (is-a?/c edible<%>) void?)] [draw (->m pict?)]))
> (define/contract checked-fish% fish/c fish%)
> (send (new checked-fish%) eat (new tire%)) eat method in fish%: contract violation expected: (is-a?/c interface:edible<%>) given: (object:tire% ...) in: the 1st argument of the eat method in fish/c contract from: (definition checked-fish%) blaming: top-level at: eval:12.0
There are more combinators like object/c and so on that provide more fine-grained contract checking for classes. See the Guide section on class contracts for more details.
> (define/contract better-dory (instanceof/c fish/c) dory)
> (send better-dory eat (new tire%)) eat method in fish%: contract violation expected: (is-a?/c interface:edible<%>) given: (object:tire% ...) in: the 1st argument of the eat method in ... (instanceof/c fish/c) contract from: (definition better-dory) blaming: top-level at: eval:14.0
edible<%>. We’d like to ensure that food has some basic level of nutrition, so that our fish don’t get food poisoning and lose weight:
Given class contracts, contracts on interfaces may seen unnecessary. After all, you can always write a class contract and use it on the food:
> (define mold% (class* object% (edible<%>) (super-new) (define/public (eaten) -0.4)))
> (define ernest (new checked-fish% [color "honeydew"]))
> (send ernest eat (new mold%))
> (send ernest draw)
This successfully protects the new checked-plankton% class, but it doesn’t give us any guarantees about the original plankton%. Worse, a third party could give us a class implementing edible<%> that doesn’t come with a contract attached. In other words, we have to secure every channel through which we could potentially get an edible<%> object.
> (define nutrition/c (flat-named-contract 'nutrition (>=/c 0)))
> (define edible<%>/c (class/c [eaten (->m nutrition/c)]))
> (define/contract checked-plankton% edible<%>/c plankton%)
Of course, with higher-order contracts this isn’t as bad as it sounds. For example, we could revise fish/c in the following way to get closer to what we want:
Notice how the domain of eat is now (instanceof/c edible<%>/c), which uses the class contract we wrote above to check the edible object coming into the method.
> (define fish/c (class/c (init-field [weight real?]) [eat (->m (instanceof/c edible<%>/c) void?)] [draw (->m pict?)]))
This works pretty well, but it’s not perfect. If we write another fish method that interacts with edible things, we have to remember to use the right contract. Not only that, instanceof/c will wrap the object with a contract every time it is passed to the method, which could cause an object to be wrapped with an arbitrary number of instance contracts. The redundant contract wrappings can impose a performance cost.
Instead, since we’re the ones providing the edible<%> interface, it would be far better to just demand once and for all that anything that implements this interface has to uphold certain obligations. We can do that by adding contracts to the methods directly in the interface:
Now even though our fish/c contract does not use an instance contract, our fish is protected from eating badly implemented foods. The check is established for every class that implements the checked-edible<%> interface. The actual contract wrapping occurs when an object of a class implementing the interface is instantiated.
> (define checked-edible<%> (interface () [eaten (->m nutrition/c)]))
> (define hemlock% (class* object% (checked-edible<%>) (super-new) (define nutrition -0.5) (define/public (eaten) nutrition)))
> (define fish/c (class/c (init-field [weight real?]) [eat (->m (is-a?/c checked-edible<%>) void?)] [draw (->m pict?)]))
> (define/contract francesco (instanceof/c fish/c) (new fish% [color "plum"]))
> (send francesco draw) > (send francesco eat (new hemlock%)) eaten: broke its contract promised: nutrition produced: -0.5 in: the range of (->m nutrition) contract from: (class hemlock%) blaming: (class hemlock%)
Even better, interface contracts work together with interface inheritance to make sure that sub-interfaces uphold the invariants that their super-interfaces guarantee. More concretely, suppose we define a new sub-interface of checked-edible<%>:
Francesco gets quite fat. This is fine, of course, since the healthy variant of food correctly upholds the edible<%> invariants. Any healthy<%> food is guaranteed to have non-negative nutrition. In contrast, suppose we define a poison<%> interface as follows:
> (define healthy<%> (interface (checked-edible<%>) [eaten (->m (>=/c 0.5))]))
> (define kale% (class* object% (healthy<%>) (super-new) (define/public (eaten) 1)))
> (send francesco eat (new kale%))
> (send francesco draw)
then we get a contract violation. Not only that, it appropriately blames the poison<%> interface for incorrectly implementing the checked-edible<%> interface. In other words, interface contracts allow us to ensure that sub-interfaces are behavioral subtypes of super-interfaces.
> (define poison<%> (interface (checked-edible<%>) [eaten (->m (<=/c 0))]))
> (define toxic-sludge% (class* object% (poison<%>) (super-new) (define/public (eaten) -1)))
> (send francesco eat (new toxic-sludge%)) eaten: broke its contract promised: nutrition produced: -1 in: the range of (->m nutrition) contract from: (interface poison<%>) blaming: (interface poison<%>)