2012-11-13

Contracts for object-oriented programming

Contracts are a key tool for writing robust Racket programs. They make it possible to write expressive dynamic checks using the full power of the language. Even better, they provide blame information that helps pinpoint where faults occur in the program.

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.

Class contracts

First, we’ll look at simple uses of class contracts. Our running example will be designing classes for use in an aquatic game involving fish. If you are following along in a REPL, you will need to (require slideshow/pict). We can define a simple fish class:
> (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)
image
Our fish% objects will be able to eat some edible things and grow in size. Of course, we need some edible things:
> (define edible<%>
    (interface () eaten))
> (define plankton%
    (class* object% (edible<%>)
      (super-new)
      ; not very nutritious...
      (define nutrition 0.2)
      (define/public (eaten) nutrition)))
Now a fish% can eat plankton% objects:
> (define dory (new fish%))
> (send dory eat (new plankton%))
> (send dory draw)
image
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 tire%
    ; don't eat this
    (class object% (super-new)))
> (send dory eat (new tire%))
send: no such method
  method name: eaten
  class name: tire%
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.

For example, we can ensure that checked-fish% only eat edible things:
> (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
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/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
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.

Interface contracts

Up until now, we’ve only seen contracts for classes, not interfaces like 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:
> (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)
image
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 nutrition/c (flat-named-contract 'nutrition (>=/c 0)))
> (define edible<%>/c
    (class/c [eaten (->m nutrition/c)]))
> (define/contract checked-plankton% edible<%>/c plankton%)
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.

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:
> (define fish/c
    (class/c
     (init-field [weight real?])
     [eat (->m (instanceof/c edible<%>/c) void?)]
     [draw (->m pict?)]))
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.

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:
> (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)
image
> (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%)
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.

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<%>:
> (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)
image
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 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<%>)
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.

Conclusion

Interface contracts and class contracts are both useful in their separate ways: class contracts when you want fine-grained control over which classes or instances are protected and interface contracts when you want all implementors to uphold certain invariants. Racket now provides both for the discerning programmer.

1 comments: