A Taste of Nested Classes, part 3

(Continues from part 2).

After an unplanned hiatus caused by various real life issues including the irritating surprise of becoming unemployed in these interesting times, here is the long-promised third part of the nested classes series.

Class nesting in our earlier example represented the natural nesting relationship of an asteroid object inside the game that contained it. Implicit receiver sends were a convenient mechanism of communicating with the game from inside an asteroid instance “for free”, without the need to maintain an explicit object reference. Today we look at a few examples with implicit sends of a slightly different flavor.

In a typical Smalltalk system, methods in the Object class define the behavior common to all objects. Some of them, such as class or identityHash are implemented to return some interesting information extracted from the receiver. Others, such as isNil are overridden in subclasses to vary their behavior polymorphically, again depending on the receiver object. In both of these cases, the receiver is involved one way or the other.

There is a third kind, methods like halt, error:, or flag: (in Squeak). They don’t extract any information from the receiver. Also, they are not overridden, so the implementation in Object tells the full story of their behavior. We send them to self and expect that they behave the same regardless of what self happens to be. As far as these messages are concerned, the receiver itself is unimportant.

These messages are utilities representing “ambient” functionality—something potentially useful everywhere in the code. We send them to self because it’s the most readily available receiver, and they are implemented in Object so that the sends work regardless of what self happens to be. Object in this case doubles as the container of these utility functions and inheritance makes them globally visible.

This global visibility is what also makes such utilities potentially problematic. Suppose I want to have a logError: utility that writes an entry into a log file, and I want to use it throughout my application. It’s fine to have it in Object only as long as another application doesn’t decide to use the same message but log the error to the system console instead.

Here is an alternative way to implement such utilities in Newspeak.

class AsteroidsGame = (...)
(
    ...
    class Space = (...)
    (
        moveObject: anObject to: position = (
            ...
            logError: 'invalid position'
        )
        ...
    )

    class Asteroid = GameObject (...)
    (
        respondToHit = (
            isLive ifFalse: [logError: 'invalid asteroid'].
            ...
        )
    )
    ...

    logError: message = (
        errorStream nextPutAll: message; cr.
    )
)

We put logError: in the top-level class representing our application. Any method of any nested class can invoke that utility through an implicit receiver send. The superclass of the nested class doesn’t matter—the utility is accessible because the class is nested in the one that provides it. Compared to the Smalltalk example, the utility is now available everywhere in our application and nowhere outside of it.

Here is another thing that’s interesting about the Newspeak alternative. In Smalltalk, logError: would have to be an extension method of Object. Extension methods are officially ungood in Newspeak, and the example shows the goodthink alternative.

In fact, such use of class nesting as a scoping mechanism for utilities solves an entire class of data conversion problems traditionally solved by extensions. Say, we want to convert an integer to a string with its representation as a Roman numeral. A common solution would be to add an asRomanNumeral extension method to Integer to do the conversion. This is convenient because we can write x asRomanNumeral anywhere, but has the disadvantage of the function leaking into the rest of the system. A more controlled alternative of implementing a convertToRomanNumeral: method in one of the application classes has the disadvantage of being easily accessible only in the implementing class and its subclasses. In other places we need to arrange for a reference to an instance that understands the message. In Newspeak, convertToRomanNumeral: can be a method of the application (module) so that it’s easily accessible, but only within the module.

Here is an interesting hypothetical example that continues the same theme. Suppose we implement the following method in a top-level class:

if: condition <Boolean> then: then <Block> else: else <Block> = (
    ^condition ifTrue: then ifFalse: else
)

Anywhere in the class and its nested classes we can now write conditionals in this more familiar to the general public style:

if: a < b
then: [^a]
else: [error: 'invalid a']

This example is hypothetical because it could only be fast enough on a very sophisticated VM with a good adaptive optimizer. But as far as less common and therefore less time-critical control messages go, they are no different from other utilities. We can define them in the top-level application class and use them throughout the application. For example:

^return: 5 if: #bar notUnderstoodIn: [foo bar]

defined in a top-level class as

return: value <Object> if: selector <Symbol> notUnderstoodIn: block <[]> = (
    ^block
        on: MessageNotUnderstood
        do: [:ex |
            ex message selector = selector
                ifTrue: [ex return: value]
                ifFalse: [ex pass]]
)

To sum up, some methods define the kind of ambient functionality that is used throughout an application. For that reason it’s best if it can be accessed implicitly, without arranging for an explicit reference to the object that provides it. In classic Smalltalk, the only mechanism to support that is inheritance, with the disadvantage that the functionality defined this way escapes the defining module. Nested classes and implicit receiver sends in Newspeak provide an alternative mechanism that keeps such ambient operations contained inside the module.

(Continues in intermission).

2 Responses to “A Taste of Nested Classes, part 3”

  1. Travis Griggs says:

    But why do we not want to “arrange for an explicit reference to the object that provides it?” As you point out, sending it to “self” is a sort of misnomer. A (the?) point of object oriented programming is to bind behavior to data, and there’s really no obvious connection between the “self” and the function. The CLOS guys get around this by just having generic functions. They have an OO system wouldn’t be having this dilemma.

    An alternative in Smalltalk is to just create a “utility functions” class. This leads to awkward things like SmalltalkWorkbench doSomething and SystemUtils do something else. And you have to locate and then code the long reference, which is not as simple/terse as just ‘self’. Probably my favorite “simple solution” to this, that I’ve seen, without doing the more involved solution you lay out (which is of course appropriate for newspeak which wants to embrace the implicit receiver syntax), was the way Smalltalk/X did this. Smalltalk/X has nested classes. So it is common to have a “namespace” class for your application MyThing, and then put your classes in it (and in a package by the same name consequently). This is similar to VisualWorks, but unlike VisualWorks, this “namespace” is a first class Behavior, capable of defining methods/services appropriate for the application, rather than VW’s cold/dead “declarative” namespaces. It’s a rather cheap/simple/easy/cheesy solution, but I found what little I did with it, it worked very well. I would just add MyGame logError: aString messages to my code. This seemed more expressive anyway of using self as the receiver. It instantly identified the method for what it likely was.

Leave a Reply

For spam filtering purposes, please copy the number 8578 to the field below: