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 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, 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] the @scheme[class/c] form is used. The @scheme[class/c] form has many
protects the public fields and methods of objects instantiated from the subforms, which describe two types of contracts on fields and methods:
contracted class. There is also an @scheme[object/c] form that can be used those that affect uses via instantiated objects and those that affect
to similarly protect the public fields and methods of a particular object. subclasses.
Take the following definition of @scheme[animal%], which uses a public field
for its @scheme[size] attribute: @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[ @schemeblock[
(define animal% (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])))) (define giant (new (class object% (super-new) (field [size 'large]))))
(send bob eat giant)] (send bob eat giant)]
There are two important caveats for these contracts, which we will There are two important caveats for external class contracts. First,
call @deftech{external class contracts}. First, external method contracts external method contracts are only enforced when the target of dynamic
are only enforced when the target of dynamic dispatch is the method dispatch is the method implementation of the contracted class, which
implementation of the contracted class, which lies within the contract lies within the contract boundary. Overriding that implementation, and
boundary. Overriding that implementation, and thus changing the target thus changing the target of dynamic dispatch, will mean that the contract
of dynamic dispatch, will mean that the contract is no longer enforced is no longer enforced for clients, since accessing the method no longer
for clients, since accessing the method no longer crosses the contract crosses the contract boundary. Unlike external method contracts, external
boundary. Unlike external method contracts, external field contracts field contracts are always enforced for clients of subclasses, since fields
are always enforced for clients of subclasses, since fields cannot be cannot be overridden or shadowed.
overridden or shadowed.
Second, these contracts do not restrict subclasses of @scheme[animal%] Second, these contracts do not restrict subclasses of @scheme[animal%]
in any way. Fields and methods that are inherited and used by subclasses in any way. Fields and methods that are inherited and used by subclasses
@ -908,6 +913,8 @@ both caveats:
(send elephant eat (new object%)) (send elephant eat (new object%))
(get-field size elephant)] (get-field size elephant)]
@subsection{Internal Class Contracts}
Notice that retrieving the @scheme[size] field from the object Notice that retrieving the @scheme[size] field from the object
@scheme[elephant] blames @scheme[animal%] for the contract violation. @scheme[elephant] blames @scheme[animal%] for the contract violation.
This blame is correct, but unfair to the @scheme[animal%] class, 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%] are protected as before, but also ensure that subclasses of @scheme[animal%]
only store appropriate values within the @scheme[size] field and use only store appropriate values within the @scheme[size] field and use
the implementation of @scheme[size] from @scheme[animal%] appropriately. the implementation of @scheme[size] from @scheme[animal%] appropriately.
These contract forms only affect uses within the class hierarchy, so the These contract forms only affect uses within the class hierarchy, and only
@scheme[override] form does not automatically enter subclasses into for method calls that cross the contract boundary.
obligations when objects of those classes are used. Instead, such contracts
are only checked when uses of the method within the superclass dynamically That means that @scheme[inherit] will only affect subclass uses of a method
dispatch to the subclass's implementation. The following example shows until a subclass overrides that method, and that @scheme[override] only
this difference: 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[ @schemeblock[
(define/contract glutton% (define/contract sloppy-eater%
(class/c (override [eat (->m edible/c void?)])) (class/c [eat (->m edible/c edible/c)])
(class animal% (begin
(super-new) (define/contract glutton%
(inherit eat) (class/c (override [eat (->m edible/c void?)]))
(define (gulp food-list) (class animal%
(for ([f food-list]) (super-new)
(eat f))))) (inherit eat)
(define sloppy-eater% (define/public (gulp food-list)
(class glutton% (for ([f food-list])
(super-new) (eat f)))))
(define/override (eat f) (class glutton%
(let ([food-size (get-field size f)]) (super-new)
(set! size (/ food-size 2)) (inherit-field size)
(set-field! size f (/ food-size 2)) (define/override (eat f)
f))))] (let ([food-size (get-field size f)])
(set! size (/ food-size 2))
(set-field! size f (/ food-size 2))
f)))))]
@interaction-eval[ @interaction-eval[
#:eval class-eval #:eval class-eval
(begin (define/contract sloppy-eater%
(define/contract glutton% (class/c [eat (->m edible/c edible/c)])
(class/c (override [eat (->m edible/c void?)])) (begin
(class animal% (define/contract glutton%
(super-new) (class/c (override [eat (->m edible/c void?)]))
(inherit eat) (class animal%
(define/public (gulp food-list) (super-new)
(for ([f food-list]) (inherit eat)
(eat f))))) (define/public (gulp food-list)
(define sloppy-eater% (for ([f food-list])
(eat f)))))
(class glutton% (class glutton%
(super-new) (super-new)
(inherit-field size) (inherit-field size)
@ -991,6 +1007,26 @@ this difference:
(get-field size slop1) (get-field size slop1)
(send pig gulp (list slop1 slop2 slop3))] (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] @close-eval[class-eval]