Argument by irrelevant dichotomy

Created 4th October, 2007 09:19 (UTC), last edited 4th October, 2007 11:59 (UTC)

Erik Naggum is of course famous for his strong views about the Right Way™ to do things. In this post, he waxes lyrical on object orientation as seen through CLOS (via Raganwald).

Reading the post I can smell dogma, and if there's one thing I dislike in any discussion about programming technique it is dogma. Let's look at just one point he makes.

There are two real approaches to object-orientation. The first is known as message-passing. … The second approach is generic functions.

The discontinuous mind strikes again.

This is a good way to characterise the opposite poles of two ways of looking at object oriented systems. They're not good places to try to shoehorn all sorts of different OO systems into. Poles are useful because they illuminate our understanding by showing extremes that we can use to measure other things by. They are not useful if we insist of thinking of everything we encounter as being at one or the other extreme.

Clearly Erik's preference is for generic functions, for which he describes many details whilst completely forgetting to describe the most important one: The search algorithm used to order the generic function implementations so that the right bit of code gets executed. Let's look at where thinking about it leads us.

CLOS has a pretty clever algorithm that looks up the dynamic type of all of the arguments within the context of a given identifier (the function name). All of this executes at run time, although the concept of “run time” in an image based system might not be what you expect.

C++ and Java do things a little differently. They search based on the called function identifier, the dynamic type of the first argument, the static type of the other arguments¹ [1Dobbin explains why the first argument is the one to the left of the dot.] and, maybe surprisingly, the static type of the object variable. This allows a large part of the search algorithm to be run by the compiler before the program is ever executed.

Maybe this will make it easier to follow. The search uses StaticTypeOfVariable, StaticTypeOfArgument1, StaticTypeOfArgument2 and function_identifier at compile time and DynamicTypeOfVariable at run time:

StaticTypeOfVariable my_var = new DynamicTypeOfVariable();
StaticTypeOfArgument1 argument_1 = ...
StaticTypeOfArgument2 argument_2 = ...

my_var.function_identifier( argument_1, argument_2 );

Smalltalk takes things in a different direction. Functions don't have names, but the arguments do. The identifier name that is to be searched for is the concatenation of all the argument names and what is searched is the dynamic type of the receiver (actually its class definition and that of all of its super-classes). This has to execute in its entirety during run time, except for the case where there is only a single definition of a method when an optimisation could be made.

This uses searches for namePart1:namePart2: within the type of the object returned by MetaObjectInstance new:

object := MetaObjectInstance new.
object namePart1: arg1 namePart2: arg2.

In JavaScript there aren't even classes. The identifier that is searched on is only the function name and the receiver itself is searched (because although it has a type, it doesn't have a class² [2Which also shows why Erik is wrong when he says “In the message-passing paradigm, you need to define a class to get any methods at all and you have to use those methods on instances of that class” because JavaScript has messages and methods but no classes.The receiver is chained to another object which for some purposes could be considered a “class”, but the reality is both more bizarre and more interesting than that.]).

So in JavaScript my_object is searched for a_function, but as the object is just an associative array a number of odd things are also true:

var my_object = new Something();
my_object.a_function( literally, anything, it, doesnt, matter );
my_object[ "a_function" ]( strangely, executes, the, same, code );

Erlang has a different twist. There is no search algorithm at all! At least not at the language level. The message receiver is itself responsible for what it does and has a very clever switch syntax through which it can examine various parts of the message to decide what to do.

an_object() ->
        message1 -> ...
        message2 -> ...

So, the differences between generic functions and message passing can all be explained by differences in the search functions used to go from a name and some arguments to a bit of code to execute.

Once you understand that it all hinges on just this search algorithm it opens up the understanding and possibility that we can write our own to match what we want a system to do. I think this is a far more mind blowing than any single thing to do with generic functions.

P.S. I presume that the Peter Seibel he is replying to is this one.