Lecture: 6 Date: Jan 17, 2008 - Writing methods for unions. Announcements: There are lecture notes on the web. These are often corrected/clarified/expanded versions of what I say in class. Dave Herman---yes, the Dave Herman---of JavaScript fame (maybe you've heard of it?) is giving a PhD proposal at 3pm. The subject is Scheme macros. If you've ever wanted to know what a research talk or being a PhD student is all about, come sit in. Q: Any Q's? === +----------+ | IShape | +----------+ / \ / \ --- --- | | +----------+ +------------+ | Square | | Rectangle | +----------+ +------------+ | Posn loc | | Posn loc | // Northwest corner position. | int size | | int height | +----------+ | int width | +------------+ ;; A Shape is one of: ;; - (make-square Posn Number) ;; - (make-rectangle Posn Number Number) (define-struct square (loc size)) (define-struct rectangle (loc height width)) Q: Write an example in Java. A: class ShapeExample { IShape s1 = new Square(new Posn(10,10), 5); IShape r1 = new Rectangle(new Posn(0,5), 10, 20); } In Scheme: (define s1 ...) (define r1 ...) Develop this function (in Scheme): ;; compute the area of the given shape. ;; area : Shape -> ? Number Make examples: (area s1) => ? size2 = 25 (area r1) => ? height*width = 200 Template: (define (area s) (cond [(square? s) ...] ; Q: Why cond? [(rectangle? s) ...])) ; Q: What goes in the branches? (define (area s) (cond [(square? s) ... (square-loc s) ... ; Posn ... (square-size s) ...] ; Number [(rectangle? s) ... (rectangle-loc s) ... ; Posn ... (rectangle-height s) ... ; Number ... (rectangle-width s) ...])) ; Number Code: (define (area s) (cond [(square? s) #| ... (square-loc s) ... ; Posn ... (square-size s) ...] ; Number |# (* (square-size s) (square-size s))] [(rectangle? s) #| ... (rectangle-loc s) ... ; Posn ... (rectangle-height s) ... ; Number ... (rectangle-width s) ...])) ; Number |# (* (rectangle-height s) (rectangle-width s))])) Develop this program in Java. Write the data definitions: // Represents a shape. interface IShape {} // Represents a square. class Square implements IShape { // Fields Posn loc; // Northwest corner position. int size; // Constructor Square(Posn loc, int size) { ... } } // Represents a rectangle. class Rectangle implements IShape { // Fields Posn loc; // Northwest corner position. int height; int width; // Constructor Rectangle(Posn loc, int height, int width) { ... } } Q: What's the first step in the recipe? A: Contract & purpose. Purpose stays the same. Q: Where to we see the contract? A: In the "header" of the method, ie: int area() { ... } In general: Z f(A a, B b, C c) { ... } Translate this to a Scheme contract: f : T A B C -> Z Where f is a method in the T class. Now go the other way: g : X Y Z -> Q Translate to Java header (contract): Q g(Y y, Z z) { ... } Which is in the X class definition. Methods must be invoked (called) by an instance of a class. Data and the functions that operate on that class of data are "bundled" together. When an instance invokes a method "this" refers to the instance that did the invoking. So translate a function call: (f p q r) = p.f(p, q, r); Q: Where do we put the definition of the area() method? A: In the class. But which? Both. OK, template? We had two templates (one in each arm of the cond). These are translated, one in each variant of the union. class Square implements IShape { ... // to compute the area of this square. int area() { /* ... this.loc ... // Posn ... this.size ... // int */ } } class Rectangle implements IShape { ... // to compute the area of this rectangle. int area() { /* ... this.loc ... // Posn ... this.height ... // int ... this.width ... // int */ } } In the template, "s" becomes "this". Q: What happened to the cond? Java does the cond. It invokes the right method based on what kind of data is invoking the method. Q: Code? return this.size * this.size; // in Square return this.height * this.width; // in Rectangle So each clause in the cond corresponds to a variant in the union. You put code in the class that corresponds to that variant. AND you add the method header to the interface. interface IShape { // to compute the area of this shape. int area(); } Now let's do bigger. ;; is the given shape bigger than the other? ;; bigger? : Shape Shape -> Number We start coding... (define (bigger? s1 s2) (cond [(square? s1) ...] [(rectangle? s1) ...])) But what about s2? (define (bigger? s1 s2) (cond [(square? s1) (cond [(square? s2) ... [(rectangle? s2) ...])] [(rectangle? s1) (cond [(square? s2) ...] [(rectangle? s2) ...])])) But what goes in each ...? (> (area s1) (area s2)) But wait---that's the same in each case! Simplify: (define (bigger s1 s2) (> (area s1) (area s2))) OK, now let's translate: - take the code from the square? part of the cond and translate it into the Square method for bigger. - take the code from the rectangle? part of the cond and translate it into the Rectangle method for bigger. Wait: WHAT COND?! There is no cond! So take the code and translate it into the Square bigger and Rectangle bigger. Yes, the same code. Duplicate it. Oh, but remember, you should never duplicate code. For now, we'll have to live with duplicating the code. We'll see as we advance how to abstract this out, but it involves more class hierarchy machinery that we haven't seen yet. So we have: Examples: s1.bigger(r1) -> false r1.bigger(s1) -> true r1.bigger(r1) -> false class Rectangle ... { ... // is this rectangle bigger than the given shape? boolean bigger(IShape that) { /* Template this.loc ... Posn this.height ... int this.width ... int this.area() ... int that.area() ... int Q: Do we have that.loc? */ return this.area() > that.area(); } } class Square ... { ... // is this square bigger than the given shape? boolean bigger(IShape that) { // Same code as above. return this.area() > that.area(); } } The interface gives you a common name and common methods for all the variants of the union. Every class that is a variant of this union must claim that it implements the interface and must define all of the methods given in the interface. Add methods to your template. Every method in a class has a template that includes the fields and methods of this class. Thus we can put a common template in the class itself and add only the specific additions that are needed in each method definition. class Name ... { /* Template // Fields this.field1 ... Type1 this.field2 ... Type2 ... etc. ... // Methods this.method1(TypeA, ...) ... Type1 this.method2(TypeB, ...) ... Type2 ... etc. ... */ Type1 method1(TypeA a, ...) { /* Template no need to copy above template, but we may know other things that should appear here. */ } Let's do within---is a given point within this shape. ;; within? : Shape Posn -> Boolean (define (within? s p) (cond [(square? s) ...] [(rectangle? s) ...])) Translate: interface IShape { ... // Is the given point within this shape. boolean within(Posn p); } class Rectangle ... { ... boolean within(Posn p) { /* Template Q: Do we put bigger(IShape) here? A: No, we don't have two shapes to compare. p.x ... int p.y ... int */ } } Same thing for Square. Example: (within? s1 (make-posn ? ?)) How many do we need? Look at the variants. We need true/false for each one, ie. 4 examples. You do it and translate to Java. How does this computation work? Maybe we need some helpers like inInterval. We'll come back to finishing the code next time. Look again at the contract for within?. Could we have instead done: ;; within? : Posn Shape -> Boolean (define (within? p s) (cond [(square? s) ...] [(rectangle? s) ...])) Of course. What would it mean for the translation though? Q: What class would within be define in? A: Posn Q: Is this the appropriate place? A: No-- within has to do with Shapes and Posns shouldn't have to know anything about shapes. Also notice that if within were defined in Posn, how would we know if the shape was a Rectangle or a Square??? If you ever wonder which variant of a union you have in hand, you've probably defined something in the wrong place.