The following is a brief discussion of classes and the issues with subclassing. It was originally written with the intention of explaining thr trouble to some of my office-mates. I’ve become more interested in a reasonable resolution to the dynamic dispatch problem (which, to be accurate, has two well-defined solutions that I find unsatisfactory). I’ve recently read the Classboxes proposal, which I find grossly unsatisfactory (after partially implementing the semantics, but that’s another post), and I decided to look into the problem.
First, let’s suppose we’re creating objects in a language like Racket. We can start by defining a small object that has a subobject:
The first defines a set of functions and constructs an object that
has the two functions (called methods) that are mutually recursive. The
sub-object defines a new object that inherits even
from obj1
but
redefines odd
to always return true. What happens if we invoke these
functions?
Let’s consider this:
That worked as we hoped! But why? Because we’ve cleverly made odd look “the same” in this example and only fed it odd numbers. Let’s consider another call:
Let’s try defining a new define another subclass:
Let’s try the same calls:
Why did that work? Well, ‘work’? Because of Lexical Scope: because we’ve decided to close over odd in our first definition of even. But objects don’t really work this way. Let’s add self!
This is how it works in “modern languages”. Now let’s see our subclassing:
Now, some more calls:
And boy is that a problem! We’re relying on Dynamic Scope to do what we’d like here. What’s worse, we could imagine that odd is a private function, which means a subclasses might shatter security:
Now we can imagine encrypt and key are private here, right? And safely call
encrypt-with-private-key
:
But a subclass will allow me to only rewrite anything I please, and scope it the way I want! Note I am calling getters here, but that’s what any subclassing compiler is going to do, anyway.
Now I can try to encrypt again!
While this isn’t really ‘a serious error’ in some sense, what have we reconstructed? The ability to arbitrarily perform superclass reflection via subclassing! And in a language where functions are first-class, I might even capture private methods defined on a class using a subclass’s definition in this model.
Okay, so this has been problematic, because of Dynamic Scope. Let’s try it the other way, so that we maintain lexical scoping.
Self is no longer exposed to the user, and our subclass utterly fails to be tricky:
There’s our safty back! We can no longer change the underlying definition of any method or field in the superclass using this model.
But what have we lost? Local self-reference!
Dang, now we can’t change our keys out!
So it’s not actually enough to specify things how we’ve done: neither lexical or dynamic scope is quite what we want. We’d like to choose ones with some escape mechanism, which is at least partially-described in Dan Friedman’s Object-Oriented Style. Even so, the paper provides the following discussion of scopeis: “This decision is quite arbitrary, but any reasonable characterization of which variables shadow which over variables can be [easily implemented].”
Most modern language pick one solution (like explicitly requiring super
, or
implicity shadowing things). Unfortunately, there is significant trade-off in
every case, and I can’t help but think there’s a better solution…