Lectures
Lecture 1: The Essence of Objects
Lecture 2: Unions of Objects
Lecture 3: Classes of Objects:   Data Definitions
Lecture 4: Classes of Objects:   Interface Definitions
Lecture 5: Interface Design:   Independent and Extensible
Lecture 6: Parametric Interface Definitions and Methods
Lecture 7: Introducing Java:   Syntax and Semantics
Lecture 8: Union, Interfaces, and Lists in Java
Lecture 9: Testing in Java
Lecture 10: Parametric Interfaces in Java
Lecture 11: Computations on Many Structural Arguments:   Double Dispatch
Lecture 12: Parameterized Types and Double Dispatch; Abstracting Values
Lecture 13: Abstracting Computation with Function Objects
Lecture 14: Function Objects & Parameterized Types; Anonymous Classes & Lambda
Lecture 15: The Fundamental List Abstraction:   Fold
Lecture 17: Midterm Review
Lecture 16: Properties of Equality:   Reflexive, Symmetric, Transitive, and Total
Lecture 19: Structural Equality with Double Dispatch; Abstracting and Overridding
Lecture 18: More Double Dispatch
Lecture 22: Optional, Maps, Sets, and Lifting Default Code to Abstract Classes
Lecture 23: The Visitor Pattern
Lecture 24: Implementing Visitors; Bank Accounts
Lecture 25: Imperatives:   Implicit Communication via Side-Effects
Lecture 26: Aside:   List Exercises
Lecture 27: Imperatives:   Cyclic Data
Lecture 28: Imperatives:   Methods over Cylic Data
Lecture 29: BSTs, Maps, The Law of Hash  Code, and Comparable vs Comparators
Lecture 30: Random access and Array  Lists
Lecture 31: Implementing Hash Tables
Lecture 32: Resizing Hash Tables
Lecture 33: Simple Iterators
Lecture 34: List Iterators and Iterator Combinators
Lecture 35: List Iterators and Iterator Combinators
Lecture 36: Zippers
Lecture 37: Naive Tree Iterators
Lecture 38: Efficient Pre-Order Tree Iterators
Lecture 39: Drills
Lecture 40: Drill Solutions
Lecture 41: Wrap-up
6.12

Lecture 2: Unions of Objects

Video 2019. Video 2018.

We’ve already seen the fundamental idea of objects, that an object is the pairing together of data and functionality. Over the next few lectures we will explore what that means in terms of program designs that we’re familiar with.

(In class, we reviewed the basics of how objects and method invocation work for a simple example of compound data: coordinates. These topics are covered in Lecture 1: The Essence of Objects.)

Let’s take a look at a data definition that involves a union. First, let’s look at the design we are familar with:

;; A Shape is one of:
;; - (make-circ Real)
;; - (make-sq Real)
;; Interp: either a circle or a square
 
(define-struct circ (radius))
(define-struct sq (size))

Here we are defining a set of values called Shape and a Shape is either a circle or a square. Circles are constructed using the make-circ constructor, given a radius for the circle, and squares are constructed using the make-sq constructor giving the size of the edge of the square.

Knowing the data definition, we can write the template for any function that operates on shapes. The template consists of a cond to determine which variant of the union was given as the argument to the function, and then within each branch of the cond, the function can deconstruct the structure by accessing its fields:

;; shape-template : Shape -> ??
(define (shape-template s)
  (cond [(circ? s) (... (circ-radius s) ...)]
        [(sq? s)   (... (sq-size s) ...)]))

Now let’s write a particular function on shapes, the function for computing the area of a shape:

;; area : Shape -> Real
;; Compute the area of the given shape
(check-expect (area (make-sq 3)) 9)
(check-within (area (make-circ 5)) (* 25 pi) 0.0001)
(define (area s)
  (cond [(circ? s) (* pi (sqr (circ-radius s)))]
        [(sq? s) (sqr (sq-size s))]))

This is all familiar stuff. Let’s consider what happens when we switch to using objects.

The first observation to make is that in the above code, we define the concept of Shape, but there’s no Shape structure. There are only circ and sq structures. Analogously, there will not be a Shape class, but rather we will define Shape as the union of classes for circles and squares. Designing the class-based analogs of circ and sq is pretty straightforward using the ideas we saw in Lecture 1: The Essence of Objects for representing compound data with objects.

Focusing just on the data definition (not the functionality yet), we get:

;; A Shape is one of:
;; - (new circ% Real)
;; - (new sq% Real)
;; Interp: either a circle or a square
 
(define-class circ%
  (fields radius))
 
(define-class sq%
  (fields size))

Next we can think about the method for area. If we think about writing a method for each kind of shape, the problem is pretty simple. We can write a method computing the area of a circle as follows:

circ%

;; area : -> Real
;; Compute the area of this circle
(define (area)
  (* pi (sqr (send this radius))))

A note on formatting: we use the above convention to mean that the code belongs inside the circ% class definition. In other words, we’re really saying:

(define-class circ%
  (fields radius)
 
 ;; area : -> Real
 ;; Compute the area of this circle
 (define (area)
   (* pi (sqr (send this radius)))))

The area method for squares is straightforward too:

sq%

;; area : -> Real
;; Compute the area of this square
(define (area)
  (sqr (send this size)))

Now we have methods for computing the area of squares and the area of circles, but what about Shapes? Well, we’re actually done. Consider having some value s which you know to be a Shape. You can compute its area by sending it the area method name, i.e. (send s area).

A natural thing to wonder is which method will be used? The answer is (just like in the ISL code) “it depends on what kind of shape s is.” If s was constructed with the circ% constructor, it will use the circle method for area; if it was constructed with sq%, it will use the square one. The key difference, compared with the structure and function approach is that we, the programmer, don’t have to explicitly write out the case analysis – the cond disappears in the object-oriented design.

So if the cond disappears, how does the object know which method to use? The answer here comes back to the fundamental idea of objects. Since the object has its functionality coupled together with its data it doesn’t “figure out” which method to use, it simply uses the method that is part of the object.

It will take some getting used to this new style, but this idea lies at the heart of programming in an object-oriented style. Instead of using functions which are independent of data and must explicitly inspect how the data was constructed in order to figure out what to compute, we instead couple the appropriate computation with the data in an object.

Finally, it’s worth noting the subtle difference in the signatures and purpose statements for the object-oriented program (in particular we go from a single signature and purpose statement for the area function to two for the area methods).