Going Deeper with Objects

Going Deeper

Note: Read this section for the ideas only. The code gets a bit bigger, and perhaps harder to understand. You don't have to study the code in detail, but please do think about the ideas.

There are other semantic features of OOP that we might want to have:

Let's consider polymorphism first, as it requires less new machinery.

Dynamic Polymorphism

What, if anything, do we have to do to implement dynamic polymorphism using our closure-based objects? Racket is dynamically typed, so we already have the ability to treat any object like some other. Any selector procedure that accepts withdraw and deposit messages can act as a bank account. Client code wouldn't know the difference. This means that dynamic polymorphism is free in our implementation of OOP.

If we create a class-based implementation of OOP that checks types, we will need to implement a mechanism to allow polymorphic objects.

Classes and Inheritance

Inheritance is a challenge for this style of implementing OOP, because it does not explicitly represent the idea of a class.

If your only exposure to objects is through Python or Java, you may be surprised to learn that not all object-oriented languages have classes! Consider Self, a language designed at Sun Microsystems (now owned by Oracle) beginning in the mid-1980s.

In Self, there are no classes, only objects. You create a new object by "cloning" another object, called a prototype. You create a new kind of object by adding new state and behavior to an existing object. Self is quite cool and has influenced many languages since. You can do some things much more simply and elegantly in Self than in a class-based OO language.

The most popular and influential prototype-based language these days is Javascript. It is a language with broad application and remains a hotbed of development, thanks to the role it plays on the web.

In a dynamically-typed language such as Racket, classes might play a less important role than in a language such as Java. But how might we implement them?

If we think about classes in a different way, we can implement something simple that captures the idea. What if we think of a class as an object that can create instances for us? In that sense, we already know how to implement a class: use the same sort of closure that we use to implement objects!

If you'd like to see this idea in action, take a look at this Racket file, which defines make-account as a function that responds to the message new. In response, it returns a constructor for a bank account. It even has class variables (often called 'static variables' in Java and C++) that we can interact with through the class. Here is a sample interaction:

> (define eugene ((make-account 'new) 100))
100
> ((eugene 'balance))
100
> ((eugene 'deposit) 100)
200
> ((eugene 'balance))
200
> ((bank-account 'count))
1
> (define mary ((make-account 'new) 100))
> ((bank-account 'count))
2

In this approach, a class is a function that returns a new object to us. The new object is... a function.

Of course, this new style of bank account is a closure, too, so we don't have to use the parenthesized syntax. Our original version of send works with them:

> (define eugene (send make-account 'new 100))
> (send eugene 'balance)
100
> (send eugene 'deposit 100)
200
> (send eugene 'withdraw 50)
150
> (define mary (send make-account 'new 1000))
> (define alice (send make-account 'new 10000))
> (define bob (send make-account 'new 100000))
> (send make-account 'count)
4
> (send alice 'balance)
100000

We now have a way to implement multiple constructors, as we see them in Java and C++. We can add another case to the selector procedure returned by bank-account!

In Java, classes aren't objects to which we send messages; class is a special construct. But in a language such as Smalltalk, everything is an object. A Smalltalk class is an object to which you send a message in order to create an instance of the class. Today, languages like Ruby provide the same feature. With one more level of closure wrapping our object closure, we are able to implement a class with nothing new. As David Wheeler, a programmer in the early 1950s (!) once said,

Any problem in computer science can be solved with another layer of indirection.

Programmers of that time were solving many of the problems that cause us to bang our heads against the wall, and they did so at a time when the tools available were a lot less powerful. Often, constraints make us more creative.