Thursday, June 03, 2010

A Nest of Classes

Nested classes, like classes and OO in general, come to us from Scandinavia. The language Beta introduced nested classes in a stunningly elegant way; Java popularized nested classes in a rather different way; and Newspeak builds on them pervasively, in a manner similar to Beta, but still distinct. In this post, I want to explore these variations on nested classes.

Traditional OO is built on classes. We note an obvious property:

(1) Classes contain methods.

Beta’s core idea is something they call a pattern. A pattern in this context has nothing to do with pattern matching as we know it in functional programming. Instead, a pattern unifies classes and methods (and types and functions and procedures, but never mind all that).

Since class = pattern = method, we can perform some substitutions on the remark given above

(2) Patterns contain patterns.

This is true in Beta, even if reading this right now, you aren’t clear what it means. Let’s do a few other substitutions of equals for equals:

(3) Classes contain classes.
(4) Methods contain classes.
(5) Methods contain methods

Item (3) gives us member classes (using Java terminology); (4) gives us local classes; and (5) gives us nested procedures, like Pascal used to have.

The nice things is that all of these features come for free! This is the power of composition. Furthermore, they are all governed by consistent rules, because they fall out of the same definitions.

One can push this even further - the language gBeta unifies patterns with mixins as well.

But wait - isn’t this going a bit too far? After all, there is abundant evidence suggesting that the distinction between nouns and verbs, or objects and procedures, is fundamental to human cognition. I know some very good (former) Beta programmers who confess that the uniformity can be confusing.

That is why Newspeak doesn’t unify classes and methods this way. However, thinking about the unification is valuable even if you don’t go through with it. It will help you produce a consistent and flexible result.

To be fair, there is a school of thought that argues that different things must be kept very different, and that they can then be specialized so that they are finely tuned to a specific need. The Modula-3 report made this point nicely with the following quote:

Look into any carpenter's tool-bag and see how many different hammers, chisels, planes and screw-drivers he keeps there - not for ostentation or luxury, but for different sorts of jobs.
- Robert Graves and Alan Hodges

Language design isn't carpentry however.

Beta’s ideas served as inspiration for Java’s nested classes. However, Java is a language in the mainstream tradition, with a philosophy closer to Modula than to Beta. When nested classes were being added to Java, Java already had distinct concepts for classes, methods and variables, and even distinct namespaces for them. This makes it hard to ensure that the rules are uniform and consistent. One tries; some of the smartest people I’ve ever met recommend building tables and cross checking systematically - but really, it is incredibly difficult.

Hence, the rules for nested classes are quite different from those for methods nested in classes for example. Consider the effect of a modifier such as final. A final class means one thing (a class that may not be subclassed) a final variable another (an immutable variable) and a final method yet another (a method that may not be overridden).

One of the most important discrepancies has to do with the instance vs. static distinction. A static variable is property of a class, but an instance variable is a property of a specific instance. Conceptually, this is true of methods as well - instance methods are considered a property of an object. That is why they must be dynamically bound - two objects might have different methods. Therein lies the power of object-oriented programming. However, in Java, a nested class is never a property of an individual object. This is different from Beta (and Newspeak) and carries major implications.

If a class is a property of an object, then virtual classes arise naturally. Furthermore, the power of polymorphism applies to classes as well. Since we can abstract over objects, we can abstract over their members; those members are typically methods, which is why object oriented and functional programming are not as a different as some would make them out to be. If the members of an object (rather than a class) can be classes, we can abstract over classes. This can help avoid the difficulties that dependency injection frameworks try so awkwardly to address. We’ve realized these benefits in Newspeak.

Altogether, lack of uniformity prevents a language’s constructs from composing together easily to produce an exponential takeoff in expressivity. Instead, the rules themselves compose, yielding an exponential takeoff in interactions between the them. That is why simplicity is so crucial in language design.

Newspeak obtains its power not from unifying classes and methods, but from unifying the mechanisms by which they are referenced. Names are always treated the same - as properties of objects, which are therefore late bound and subject to override by a single set of rules.

a. A name refers to the nearest lexically enclosing declaration of that name, if there is one; otherwise, it refers to a declaration inherited from the immediately surrounding classes’ superclass.

b. Every declaration is subject to override; if you override a declaration in a subclass, it takes effect wherever the overridden declaration is used.

That’s it. Note that for a name defined by an enclosing class to be visible, it must be declared in the lexically surrounding environment; it isn’t enough for it to be inherited. You must be able to see the declaration in the surrounding classes. If you don’t see it, it isn’t there.

This is deliberate, and different from the rules you may know from Java (assuming you ever figured out what those really are) or even Beta. One advantage of this rule is that you cannot capture a name referred to in a nested class when you add a member to a superclass.

From these very simple rules (and the lack of a global namespace) we get virtual classes, mixins, class hierarchy inheritance, and powerful modularity, as I’ve described in a number of forums.

There is the small issue of specifying this behavior precisely, and beyond that, the small matter of making it work. These are not totally trivial. The spec describes the rules. As for the implementation, I plan to describe it in a paper; in the meantime, the source is out there. You want to look at changes we’ve made to the Squeak Interpreter, in particular the pushImplicitReceiver byte code as implemented in Squeak’s Interpreter class.

I hope I have convinced you that nested classes are at once simpler and more powerful than you might have imagined. As always, keeping language design simple results in a more powerful language.

8 comments:

  1. However, in Java, a nested class is never a property of an individual object.

    As I understand it, a Java static nested class is a class property, but a non-static nested class is an instance property. What am I missing?

    the immediately surrounding classes’ superclass

    I assume you mean "the immediately surrounding class's superclass". Surely the immediately surrounding class is unique?

    ReplyDelete
  2. I agree with John that the statement about nested classes is misleading.

    Although a nested class itself is not associated with any object, instances of a non-static nested class will be implicitly associated with an instance of the enclosing class. You can explicitly refer to this enclosing instance by writing "EnclosingClass.this".

    What's missing compared to methods, is that nested classes are resolved statically, there is no dynamic lookup. If I understand correctly, that's what virtual classes give you.

    ReplyDelete
  3. John, Bruno,

    Exactly. A virtual class can be overridden the way a method can.

    ReplyDelete
  4. John, Bruno,

    If a class is a property of an instance, I should be able to refer to it

    class Outer { class Inner{}}
    Outer o1 = new Outer();
    Outer o2 = new Outer();
    o1.Inner; // illegal

    Now assume it was legal. Would o2.Inner differ from o1.Inner? In Java, the following is always true:

    (o1.new Inner()).getClass() ==
    (o2.new Inner()).getClass()

    Now consider

    class SubOuter { class Inner{}}
    Outer o3 = new SubOuter();

    The following is also always true in Java:

    (o1.new Inner()).getClass() ==
    (o3.new Inner()).getClass()

    because the class Inner used is determined by the static type, not the instance.

    In Beta (and Newspeak) this is not the case. Each outer class has its own distinct nested classes. I hope this makes things clearer.

    ReplyDelete
  5. John,

    I assume you mean "the immediately surrounding class's superclass".

    Yes. The immediately surrounding class *declaration* is unique, but the class is not, as it can vary with the instance, and in particular, its superclass can be different.

    ReplyDelete
  6. Should not that be

    class SubOuter: Outer {}

    instead of

    class SubOuter { class Inner{}}

    ?

    ReplyDelete
  7. Yes. Or rather

    class SubOuter extends Outer {}

    ReplyDelete