From 9dafea226eed10c84815f272b4d2262815dc1636 Mon Sep 17 00:00:00 2001 From: Stevie Strickland Date: Fri, 26 Feb 2010 23:37:05 +0000 Subject: [PATCH 1/3] Keeping unfinished guide stuff here, will merge to trunk once done. svn: r18370 --- collects/scribblings/guide/class.scrbl | 80 +++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/collects/scribblings/guide/class.scrbl b/collects/scribblings/guide/class.scrbl index 15d7c707c3..0e82089b80 100644 --- a/collects/scribblings/guide/class.scrbl +++ b/collects/scribblings/guide/class.scrbl @@ -5,7 +5,8 @@ "guide-utils.ss" (for-label scheme/class - scheme/trait)) + scheme/trait + scheme/contract)) @(define class-eval (let ([e (make-base-eval)]) @@ -802,4 +803,81 @@ Using this form in conjunction with trait operators such as @; ---------------------------------------------------------------------- +@; Set up uses of contract forms below +@(class-eval '(require scheme/contract)) + +@section{Contracts} + +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: + +@schemeblock[ +(define animal% + (class object% + (super-new) + (field [size 10]) + (define/public (eat food) + (set! size (+ size (get-field size food))))))] + +For any instantiated @scheme[animal%], accessing the @scheme[size] field +should return a positive number. Also, if the @scheme[size] field is set, +it should be assigned a positive number. Finally, the @scheme[eat] method +should receive an argument which is an object with a @scheme[size] field +that contains a positive number. To ensure these conditions, we will define +the @scheme[animal%] class with an appropriate contract: + +@schemeblock[ +(define positive/c (and/c number? positive?)) +(define edible/c (object/c (field [size positive/c]))) +(define/contract animal% + (class/c (field [size positive/c]) + [eat (->m edible/c void?)]) + (class object% + (super-new) + (field [size 10]) + (define/public (eat food) + (set! size (+ size (get-field size food))))))] + +@interaction-eval[ +#:eval class-eval +(begin + (define positive/c + (flat-named-contract 'positive/c (and/c number? positive?))) + (define edible/c (object/c (field [size positive/c]))) + (define/contract animal% + (class/c (field [size positive/c]) + [eat (->m edible/c void?)]) + (class object% + (super-new) + (field [size 10]) + (define/public (eat food) + (set! size (+ size (get-field size food)))))))] + +Here we use @scheme[->m] to describe the behavior of @scheme[eat] since we +do not need to describe any requirements for the @scheme[this] parameter. +Now that we have our contracted class, we can see that the contracts +on both @scheme[size] and @scheme[eat] are enforced: + +@interaction[ +#:eval class-eval +(define bob (new animal%)) +(set-field! size bob 3) +(get-field size bob) +(set-field! size bob 'large) +(define richie (new animal%)) +(send bob eat richie) +(get-field size bob) +(define rock (new object%)) +(send bob eat rock) +(define giant (new (class object% (super-new) (field [size 'large])))) +(send bob eat giant)] + +@; ---------------------------------------------------------------------- + @close-eval[class-eval] From 5dd72c23c806dceef6a10b73a8830a03f5d8177b Mon Sep 17 00:00:00 2001 From: Stevie Strickland Date: Sat, 27 Feb 2010 20:57:14 +0000 Subject: [PATCH 2/3] Checkin of current prose so I can switch to trunk and fix a bug. svn: r18389 --- collects/scribblings/guide/class.scrbl | 115 ++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/collects/scribblings/guide/class.scrbl b/collects/scribblings/guide/class.scrbl index 0e82089b80..19240d7754 100644 --- a/collects/scribblings/guide/class.scrbl +++ b/collects/scribblings/guide/class.scrbl @@ -806,7 +806,7 @@ Using this form in conjunction with trait operators such as @; Set up uses of contract forms below @(class-eval '(require scheme/contract)) -@section{Contracts} +@section{Class Contracts} 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, @@ -878,6 +878,119 @@ 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. + +Second, these contracts do not restrict subclasses of @scheme[animal%] +in any way. Fields and methods that are inherited and used by subclasses +are not checked by these contracts, and uses of the superclass's methods +via @scheme[super] are also unchecked. The following example illustrates +both caveats: + +@def+int[ +#:eval class-eval +(define large-animal% + (class animal% + (super-new) + (inherit-field size) + (set! size 'large) + (define/override (eat food) + (display "Nom nom nom") (newline)))) +(define elephant (new large-animal%)) +(send elephant eat (new object%)) +(get-field size elephant)] + +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, +as we have not yet provided it with a method for protecting itself from +subclasses. To this end we add @deftech{internal class contracts}, which +provide directives to subclasses for how they may access and override +features of the superclass. We separate external and internal class +contracts to allow for weaker contracts within the class hierarchy, where +invariants may be broken internally by subclasses but should be enforced +for external uses via instantiated objects. + +As a simple example of what kinds of protection are available, we provide +an example aimed at the @scheme[animal%] class that uses all the applicable +forms: + +@schemeblock[ +(class/c (field [size positive/c]) + (inherit-field [size positive/c]) + [eat (->m edible/c void?)] + (inherit [eat (->m edible/c void?)]) + (super [eat (->m edible/c void?)]) + (override [eat (->m edible/c void?)]))] + +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: + +@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))))] + +@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% + (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 class-eval +(define pig (new sloppy-eater%)) +(define slop1 (new animal%)) +(define slop2 (new animal%)) +(define slop3 (new animal%)) +(send pig eat slop1) +(get-field size slop1) +(send pig gulp (list slop1 slop2 slop3))] + @; ---------------------------------------------------------------------- @close-eval[class-eval] From dd96465208ef13d1b2cedbab866a8fa41d483826 Mon Sep 17 00:00:00 2001 From: Stevie Strickland Date: Sat, 27 Feb 2010 21:32:11 +0000 Subject: [PATCH 3/3] Okay, finalized initial version of this, which I'll probably go ahead and merge to trunk. svn: r18392 --- collects/scribblings/guide/class.scrbl | 132 ++++++++++++++++--------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/collects/scribblings/guide/class.scrbl b/collects/scribblings/guide/class.scrbl index 19240d7754..8d1e58f42c 100644 --- a/collects/scribblings/guide/class.scrbl +++ b/collects/scribblings/guide/class.scrbl @@ -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]