Okay, finalized initial version of this, which I'll probably go ahead and
merge to trunk. svn: r18392
This commit is contained in:
parent
75dd3eeb2b
commit
dd96465208
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue
Block a user