GitHub Repo

This morning, out of the blue, I plan to assess a core aspect of my Asteroids design: the subscription model. The conclusion surprised me. (Yeah, click bait. Skip to the end if you’re that way.)

When I retire for the evening, I rarely know what I’ll write about the next morning, but quite often I’ll have been thinking about the day’s coding and, if some kind of issue has arisen, I’ll have been thinking about solutions. Sometimes I plan to work on that same problem the next day. Sometimes I plan to experiment with a solution that is starting to come clear to me, or at least looming out of the fog. Sometimes, in the morning, I actually set out to do what I planned. And sometimes, in the course of the morning, I actually do work on that thing, although it seems to me that quite commonly I follow the code more than my speculation, and that the code leads me in a different direction.

Today is different. I had no idea last night that I could, should, or would write about this. As I awoke this morning, I turned my thoughts to the program, as I often do1. I thought of the really nifty way the subscriptions things works. I thought of why it’s in there. And I thought: we need to take a look at that.

And so we shall.

The Core Design Idea

I think one would have to say that the central design notion of this version of Asteroids is the idea of small independent objects, generally with no direct knowledge of each other, aswim in an uncaring universe that doesn’t know one of them from another, does not know the difference between a ship and a star, between a lamb and a wolf, holding no intention or concerns for the particles within it, having no more care for a man and his plans than it has for the smallest mote of rock floating in space. A vast, uncaring universe in which the most elaborate construction is seen as no different from cosmic debris.

OK, maybe that’s over the top but fact is, the design here is that the universe doesn’t distinguish objects, treating them all alike, and the objects, by and large, have no master object, are not subordinate one to the other, but are all essentially independent.

And yet, when we run it, it turns out to be a game.

Philosophy? Science?
No, this program doesn’t tell us much about our real universe, at least not the key question of how it all came to be. The objects in the Asteroids universe were all crafted, rather carefully, by a designer. So we haven’t shown anything about the need for a Universe Designer, though we have shown, perhaps, an interesting way in which a designer can work.

But I didn’t set out to answer a philosophical question about the origin of things. I set out to write a program and learn a bit of IDEA and Kotlin. We’re here to assess, not what the program is, but how it does what it does.

The Objects

Our core objects have the option of responding to a number of “events”, which amount to moments in the life cycle of the universe. I chose those moments, again providing a Universe Designer shaping things. The events are:

Update
In this event, an object can learn how long it has been since the last time it saw “update”, and the object can do as it wishes with that information. If the object moves in space, this is its chance to move. The event can add or remove objects at this point.
Begin Interactions
The Universe allows each object in the Universe to interact, one at a time, with every other object. This event tells an object that this process is about to begin. The object may wish to alert itself to what’s about to happen.
Call Other
This event is sent to every object, once for every other object in the universe. If the object cares to interact, it responds to this message by triggering an event for the other. If the object is an instance of class Foo, it is supposed to trigger on the other: other.interactWithFoo(this). In this fashion, when a given object does interact, it knows exactly what the other object is.
InteractWith…
This event allows an object to determine the result of an interaction. If the object is subject to collision with asteroid, it deals with that situation in interactWithAsteroid. If it just wants to know how many asteroids there are, it initializes its count in Begin Interactions and counts them in this event. The object can add or remove objects at this time.

I think of this as a single event but in fact there are presently nine separate interactWith events, Asteroid, Missile, Saucer, Score, ScoreKeeper, Ship, ShipChecker, ShipDestroyer, and ShipMaker.

Finish Interactions
This event tells the object that interaction time is over. If anything was going to happen to it, it has happened. An object counting asteroids could use this event to decide what to do, because the counting is done and whatever count the object has is the total number of asteroids. The object can add or remove objects at this time.
Draw
In this event, the object is passed a Drawer, and if the object has a visible aspect on the game’s screen, it draws itself at this time.
Finalize
When an object is removed from the universe for any reason, it may elect to receive the finalize event, which it can use for any purpose. The object can add or remove objects at this time. Some objects take the occasion to leave behind an explosion on the screen, called a Splat.

From the viewpoint of an object in the universe, that is all there is. It can move and draw itself if it cares to. It can interact with every other object in the system. During that interaction, it can send messages to the other. This is used sparingly in practice, to get distance information, or to extract a score value. Objects do not have intimate knowledge of other objects and, I think, the less they know, the better.

A Design Dilemma

Given that we’re going to have this kind of design, we find ourselves faced with a dilemma.

On the one hand, the universe, by design, knows nothing of its resident objects, and therefore it wants to sent the same messages to each object. The object life cycle consists in always sending all those messages to each object in its turn.

On the other hand, the individual objects often do not care about all of those event messages. When creating a new kind of object, it’s a hassle to add in all those functions, some of which will not do anything, rather than simply implementing the ones that the object actually needs.

And that brings us, finally, to the central assessment that I want to make this morning, because there are a number of ways all this might be accomplished. I have tried two of them, and Hill has worked with one, invented the second, and is trying a third.

The Two Ways

The universe cycle calls a half dozen functions on all objects without regard to who or what they are. The half-dozen fan out into about thirteen with the “with” breakout. The objects would like to implement only the functions they care about. How can we do this?

Implement in Superclass
I originally accomplished this by creating a common superclass for all objects in the universe, and in that superclass, I implemented a null version of each of the functions that the universe might call. The null version generally did nothing, just returned whatever was necessary to meet the expectations of the universe, generally nothing, or an empty list in some cases2.

With this done, each of the objects in the universe was free to implement the functions that it needed to do, and it never had to concern itself with the functions that didn’t apply to it. In the simplest case, that would come down to implementing a single method out of the complete list.

With this idea carried to its conclusion, the superclass would today have to implement about thirteen default methods, one for each in the “event list” above, including each detailed interactWithFoo event. And the individual objects would implement the ones that concerned that object, and no others.

Now it did come to pass that Hill, invited to assess and assist in my learning, objected to this implementation. Apparently, perhaps owing to an event in his childhood which I have never had the temerity to ask about and which may be best forgotten, Hill has a strong dislike for concrete method implementations in superclasses. Hill is not alone in this dislike. You can find a lot of advice from books, articles, consultants, and sleazy characters on street corners, all of whom will tell you that Inheriting Implementation is a Bad Thing.

And thus it further did come to pass that the aforementioned Hill did implement a scheme that met my two criteria (R) and the added one of his own (H):

  1. R: The universe life cycle treats everyone the same;
  2. R: Objects only have to implement functions they care about;
  3. H: We don’t use implementation inheritance.
Subscriptions Object
Hill devised an object that I’m now calling Subscriptions. Each instance of that object contains a function for each of the baker’s dozen we currently require, and the object’s constructor defaults each of those functions to one that does nothing, returning whatever is needed to meet the expectations of the universe, generally nothing or an empty list in some cases3.

Now the individual objects are required to implement a new “value”, consisting of a Subscriptions instance providing specific implementations for the functions the object wants to respond to. Those specific functions will be called whenever the universe deals with this object. And when the others are called, since the constructor for Subscriptions provides a default function, no harm done, the default is called instead.

I will say right here in front of everyone that Hill’s creation of the Subscriptions object gobsmacked me at the time and even now that I understand it, I can’t imagine how he created it. It’s a really nifty bit of code.

Oversimplifying, but I hope not unfairly, Hill’s implementation replaces a baker’s dozen concrete methods in a superclass with a baker’s dozen concrete lambda functions in a subscriptions object. The functions involved literally do exactly the same thing.

In an object-oriented hierarchy, when a method function is called in a subclass, and it is not implemented, the message dispatch code in all the language calls the method in the superclass, if one exists. It bounces up the hierarchy until it finds something that meets the need. This is done behind the scenes and is as dynamic or static as the language implementation may allow. A language like Lua, Ruby, or Python will search for the superclass implementation at run time. A language like Kotlin may do the search at compile time and hook the object to the right method then.

As Hill himself has put it, with his Subscriptions idea, he has implemented “duck typing” for Kotlin. In a language with dynamic typing, if it looks like a duck, walks like a duck, and quacks like a duck, it’s a duck. In Kotlin, it might be a compile-time diagnostic, if you’re not careful about just how you jump through your own orifices.

Now, before I push on to an assessment of these two ideas, I want to mention another notion that Hill added to the implementation. In the excitement about the Subscriptions, it was almost forgotten: the Call Other.

Each object, following our current implementation of the subscriber scheme, must implement callOther, and it is expected to implement it with something like interactWithFoo(this) if it is an object of type Foo. Now, when the subscriptions idea went into the system, Hill was faced with a really nasty situation resulting from something I had done (or not done). All the possible colliding objects were, at that time, implemented in a single class, FlyingObject if I recall, and were not broken out by whether they were an asteroid, a ship, or a missile. In working with the collision aspect of the subscribers idea, Hill devised the notion of a single call, callOther, that everyone would implement, so that they didn’t have to implement as much type-oriented code in their implementation of collisions. The interactWithMissile kinds of things were the shunted through the subscribers object, so that objects could just pick out the ones that they cared about.

This “double dispatch” scheme is central to avoiding type-based switching in the functions of objects. Once you’re inside an interactWithMissile method, you know you’re dealing with a missile and you don’t have to worry about the possibility that you’re colliding with the game score or something. And the subscribers scheme provides a perfectly good way of doing the double dispatch and starting with callOther was a key contribution to getting that to work.

Subsequently, in our separate branches, both Hill and I broke out all the Flyers into separate classes for Asteroid, Missile, and so on. I’d have gotten there anyway … I just wasn’t there yet.

But it’s important to observe that the subscribers idea, interwoven with the callOther idea, has had an important and positive influence on the design of the program. It might have gone in the same direction anyway. I think that it would have. But in this stub universe, things went the way they did in large part owing to the influence of the subscribers and callOther notions.

In that light, let’s consider the two available implementations of the core design, which dictates a dumb universe with simple interacting objects.

Assessing

After requiring that every object must implement subscriptions, implementation of an individual object proceeds similarly to how it might be done with the original default method / override scheme. I say similarly. Let’s look at an example. Here’s Missile’s subscriptions:

    override val subscriptions = Subscriptions(
        interactWithAsteroid = { asteroid, trans ->
            if (checkCollision(asteroid)) {
                trans.remove(this)
                if (missileIsFromShip) trans.add(asteroid.getScore())
            }
        },
        interactWithSaucer = { saucer, trans ->
            if (checkCollision(saucer)) {
                trans.remove(this)
                if (missileIsFromShip) trans.add(saucer.getScore())
            }
        },
        interactWithShip = { ship, trans ->
            if (checkCollision(ship)) { trans.remove(this) }
        },
        interactWithMissile = { missile, trans ->
            if (checkCollision(missile)) { trans.remove(this) }
        },
    )

In the implementation inheritance scheme, I think it would look like this:

fun interactWithAsteroid( asteroid: Asteroid, trans: Transaction) {
    if (checkCollision(asteroid)) {
        trans.remove(this)
        if (missileIsFromShip) trans.add(asteroid.getScore())
    }
}

fun interactWithSaucer( saucer: Saucer, trans: Transaction) {
    if (checkCollision(saucer)) {
        trans.remove(this)
        if (missileIsFromShip) trans.add(saucer.getScore())
    }
}

fun interactWithShip( ship: Ship, trans: Transaction) {
    if (checkCollision(ship)) { trans.remove(this) }
}

fun interactWithMissile( missile: Missile, trans: Transaction) {
    if (checkCollision(missile)) { trans.remove(this) }
}

That’s not very different. I was almost able to do all the changes with multi-cursor all at the same time, except for the specific types of the first parameters.

However.

However, if there is to be a claim made as to which one of these two is simpler and more clear, I think the call has to go to the second form. The collection of lambda blocks in the first form is just more syntax to sort through … even if you don’t ask yourself what Subscriptions is and why it takes a list of named lambda parameters in its constructor. We’re making a whole new object here, my friends … and why? What does this have to do with being a Missile?

The answer is, it has nothing to do with being a Missile. It is a necessary piece of boilerplate, which must occur in every object in the universe, just to make the system work.

If we were to go back …
If we were to go back to the implementation inheritance scheme, we’d move the default lambdas in Subscriptions back to being concrete methods in a superclass of all the universe’s objects. We’d convert each creation of a Subscribers to a corresponding set of method functions in the subscribing class, just as I did above. And we’d remove val subscribers ... from every object in the universe.

I could be wrong (and over time, people could have made a lot of money betting that I was) but I believe it would be that simple to switch back to the implementation inheritance scheme. I might just do it to see whether it is in fact as easy as I think it is.

Alternatives

I don’t want to go into detail here, but subsequent to this subscribers idea, Hill has been breaking out subclasses and interfaces inside the space objects classification, identifying things as drawable, for example, and keeping separate collections of asteroids and drawables and such in the Game’s SpaceObjectCollection. Most of the type-checking is hidden in that one class.

That design is different from the one here, in a more fundamental way, because it allows the universe to know a bit more about the individual objects. The scheme here knows functions to call, Hill’s scheme, as I understand it, also knows a bit about whether or not to call the functions, or how to call them.

I don’t know that scheme well enough to assess it here. The point is, there are larger-scale design options that could come into play and result in other perfectly good designs with their own different characteristics and different trade-offs. There are many ways to partition a design, even one as simple as Asteroids, and each has merits worth considering.

For now, we’ll continue to consider just our two, subscribers vs inheritance.

Bottom Line?

So, it is down to you, and it is down to me. To me, the implementation inheritance approach is simpler. I am not troubled by the default implementations in the hierarchy, even though I know full well that if I were implementing a different kind of hierarchy, I might get in trouble. In this case, inheritance is simply a hack to avoid duplication, and so is subscribers.

I think that if one is confusing, the other must be. If a programmer is inclined to worry about what’s up in the superclass, they will have to worry about what’s up in the subscribers defaults. They might argue that in the case of subscribers, it’s guaranteed to be all there and in the case of superclasses you must, in principle, look up at all your inherited classes. Yes, maybe, but it seems that here, we’ll only have the one.

(That may turn out not to be the case. A good reason for converting, to find out?)

To you, or to Hill, perhaps implementation inheritance puts such a bad taste in your mouth that you’ll happily live with the subscribes scheme, which has the advantage of being completely under your direct control rather than hidden away in the magic of how OO works. (Although the subscribers scheme itself relies on some pretty deep language features.) Even so, I can see that someone might prefer it.

Right now
Right now, and I reserve the right to change my mind, in this and anything, I think that I prefer the implementation inheritance scheme. To better settle the matter in my own mind, I think I’ll probably go ahead and convert back to that scheme, the better to compare the two implementations.
The Real Lesson4
What is most fascinating to me, and what I’m most grateful for, is that this whole learning experience was made possible only through collaborating with Hill. Hill in particular, but also collaboration in general. I wouldn’t have had this alternative perfect workable and rather fascinating idea by myself. It is in working with colleagues like Hill, and the other most excellent people I know, that ideas come up, are assessed, are fondled and mangled, pulled apart and stuck together in new ways, that useful ideas come to be.

I’m a pretty smart guy, or at least I was. Well, in my opinion. But almost everything I know has come from someone else, through various forms of collaboration, whether reading, attending classes, or, best and most effective of all, working together.

The big lesson here is that working together isn’t just more fun, it makes me better5. And for that, I’m grateful to my very many betters out there, and in this case, particularly grateful to Hill. Long may they wave.



  1. Why do I think of programming in the morning, you (didn’t) ask? Because the other thoughts to which my mind turns do not please me, and I banish them with thoughts of code. 

  2. Remember this phrase. 

  3. Phrase sound familiar? It should: It occurs above. These two things are very much alike. 

  4. Yeah, the friends you made along the way. Trite, and true. 

  5. What should you do? You’re not going to trick me that easily into giving advice. You do you. Maybe try collaborating more if it seems interesting. Maybe not. You do you.