Okay, finalized initial version of this, which I'll probably go ahead and

merge to trunk.

svn: r18392
This commit is contained in:
Stevie Strickland 2010-02-27 21:32:11 +00:00
parent 75dd3eeb2b
commit dd96465208

View File

@ -810,12 +810,18 @@ Using this form in conjunction with trait operators such as
As classes are values, they can flow across contract boundaries, and we
may wish to protect parts of a given class with contracts. For this,
the @scheme[class/c] form is used. In its simplest form, @scheme[class/c]
protects the public fields and methods of objects instantiated from the
contracted class. There is also an @scheme[object/c] form that can be used
to similarly protect the public fields and methods of a particular object.
Take the following definition of @scheme[animal%], which uses a public field
for its @scheme[size] attribute:
the @scheme[class/c] form is used. The @scheme[class/c] form has many
subforms, which describe two types of contracts on fields and methods:
those that affect uses via instantiated objects and those that affect
subclasses.
@subsection{External Class Contracts}
In its simplest form, @scheme[class/c] protects the public fields and methods
of objects instantiated from the contracted class. There is also an
@scheme[object/c] form that can be used to similarly protect the public fields
and methods of a particular object. Take the following definition of
@scheme[animal%], which uses a public field for its @scheme[size] attribute:
@schemeblock[
(define animal%
@ -878,16 +884,15 @@ on both @scheme[size] and @scheme[eat] are enforced:
(define giant (new (class object% (super-new) (field [size 'large]))))
(send bob eat giant)]
There are two important caveats for these contracts, which we will
call @deftech{external class contracts}. First, external method contracts
are only enforced when the target of dynamic dispatch is the method
implementation of the contracted class, which lies within the contract
boundary. Overriding that implementation, and thus changing the target
of dynamic dispatch, will mean that the contract is no longer enforced
for clients, since accessing the method no longer crosses the contract
boundary. Unlike external method contracts, external field contracts
are always enforced for clients of subclasses, since fields cannot be
overridden or shadowed.
There are two important caveats for external class contracts. First,
external method contracts are only enforced when the target of dynamic
dispatch is the method implementation of the contracted class, which
lies within the contract boundary. Overriding that implementation, and
thus changing the target of dynamic dispatch, will mean that the contract
is no longer enforced for clients, since accessing the method no longer
crosses the contract boundary. Unlike external method contracts, external
field contracts are always enforced for clients of subclasses, since fields
cannot be overridden or shadowed.
Second, these contracts do not restrict subclasses of @scheme[animal%]
in any way. Fields and methods that are inherited and used by subclasses
@ -908,6 +913,8 @@ both caveats:
(send elephant eat (new object%))
(get-field size elephant)]
@subsection{Internal Class Contracts}
Notice that retrieving the @scheme[size] field from the object
@scheme[elephant] blames @scheme[animal%] for the contract violation.
This blame is correct, but unfair to the @scheme[animal%] class,
@ -935,43 +942,52 @@ This class contract not only ensures that objects of class @scheme[animal%]
are protected as before, but also ensure that subclasses of @scheme[animal%]
only store appropriate values within the @scheme[size] field and use
the implementation of @scheme[size] from @scheme[animal%] appropriately.
These contract forms only affect uses within the class hierarchy, so the
@scheme[override] form does not automatically enter subclasses into
obligations when objects of those classes are used. Instead, such contracts
are only checked when uses of the method within the superclass dynamically
dispatch to the subclass's implementation. The following example shows
this difference:
These contract forms only affect uses within the class hierarchy, and only
for method calls that cross the contract boundary.
That means that @scheme[inherit] will only affect subclass uses of a method
until a subclass overrides that method, and that @scheme[override] only
affects calls from the superclass into a subclass's overriding implementation
of that method. Since these only affect internal uses, the @scheme[override]
form does not automatically enter subclasses into obligations when objects of
those classes are used. Also, use of @scheme[override] only makes sense, and
thus can only be used, for methods where no Beta-style augmentation has taken
place. The following example shows this difference:
@schemeblock[
(define/contract glutton%
(class/c (override [eat (->m edible/c void?)]))
(class animal%
(super-new)
(inherit eat)
(define (gulp food-list)
(for ([f food-list])
(eat f)))))
(define sloppy-eater%
(class glutton%
(super-new)
(define/override (eat f)
(let ([food-size (get-field size f)])
(set! size (/ food-size 2))
(set-field! size f (/ food-size 2))
f))))]
(define/contract sloppy-eater%
(class/c [eat (->m edible/c edible/c)])
(begin
(define/contract glutton%
(class/c (override [eat (->m edible/c void?)]))
(class animal%
(super-new)
(inherit eat)
(define/public (gulp food-list)
(for ([f food-list])
(eat f)))))
(class glutton%
(super-new)
(inherit-field size)
(define/override (eat f)
(let ([food-size (get-field size f)])
(set! size (/ food-size 2))
(set-field! size f (/ food-size 2))
f)))))]
@interaction-eval[
#:eval class-eval
(begin
(define/contract glutton%
(class/c (override [eat (->m edible/c void?)]))
(class animal%
(super-new)
(inherit eat)
(define/public (gulp food-list)
(for ([f food-list])
(eat f)))))
(define sloppy-eater%
(define/contract sloppy-eater%
(class/c [eat (->m edible/c edible/c)])
(begin
(define/contract glutton%
(class/c (override [eat (->m edible/c void?)]))
(class animal%
(super-new)
(inherit eat)
(define/public (gulp food-list)
(for ([f food-list])
(eat f)))))
(class glutton%
(super-new)
(inherit-field size)
@ -991,6 +1007,26 @@ this difference:
(get-field size slop1)
(send pig gulp (list slop1 slop2 slop3))]
In addition to the internal class contract forms shown here, there are
similar forms for Beta-style augmentable methods. The @scheme[inner]
form describes to the subclass what is expected from augmentations of
a given method. Both @scheme[augment] and @scheme[augride] tell the
subclass that the given method is a method which has been augmented and
that any calls to the method in the subclass will dynamically
dispatch to the appropriate implementation in the superclass. Such
calls will be checked according to the given contract. The two forms
differ in that use of @scheme[augment] signifies that subclasses can
augment the given method, whereas use of @scheme[augride] signifies that
subclasses must override the current augmentation instead.
This means that not all forms can be used at the same time. Only one of the
@scheme[override], @scheme[augment], and @scheme[augride] forms can be used
for a given method, and none of these forms can be used if the given method
has been finalized. In addition, @scheme[super] can be specified for a given
method only if @scheme[augride] or @scheme[override] can be specified.
Similarly, @scheme[inner] can be specified only if @scheme[augment] or
@scheme[augride] can be specified.
@; ----------------------------------------------------------------------
@close-eval[class-eval]