I remember this being posted in 1998, as people were debating the surge of Java relative to C++ or Smalltalk.
These were the twilight days where C and assembly were the only language Real Programmers used, Perl and Tcl or any scripting/interpreted language were considered toys for sysops and never meant for production, OO was treated with deep suspicion, and functional languages were considered crazy talk. I remember people in my company saying a particular C++ replacement program my colleague wrote was "too OO" and would execute 10x slower than required (when it was 2x faster than the legacy).
Point is, a lot of what Kay says is obvious now, but it wasn't in the 90s, and it REALLY wasn't in the 70s.
One sees new things every time one reads this. For example, Kay comes out as a supporter (of sorts) to functional programming principles by calling assignments (mutable state) a metalevel change.
1998?... More like the twilight days when Cobol still ruled the land and we thought (feared) that it would never die. Fortunes were being made consulting on the Y2K bug and patching the dodgy Cobol systems responsible.
I do agree, though that OO was treated with suspicion and hostility. Only a tiny number of (us) mavericks were brave enough (or foolish enough) to propose using what our management saw as a "newfangled and risky" proposition.
Let's not even mention mucking about with Extreme Programming. ;)
Oh yes, COBOL was C's evil twin in that regard. It depended what circles you travelled in - engineering depts or IT depts.
I remember posting the c2 wiki pages for XP on my cube wall when XP first came out, and had a Gantt-chart oriented systems engineer tear them down as dangerous nonsense.
In a function call the arguments are applied to the function after being evaluated (LISP's apply).
In Message Passing a message (which is just another object) is sent to the receiver object.
The crucial difference is in a function call the the function is already bound when the args are sent. With message passing the args are sent to the receiver object which can handle them there or up its superclass hierarchy -- with the ability to handle messages it does not recognize too after exhausting the superclass hierarchy.
The smalltalk blue book has the VM implementation of message sending which is very straight forward.
Lisp/CLOS provides parts of that in the function-call model.
In Lisp/CLOS the function is being assembled based on the arguments. That's called 'computing the method combination'. If there this is not possible, because there are no methods defined for these argument types, the generic function NO-APPLICABLE-METHOD gets called with the the original function and the arguments - for NO-APPLICABLE-METHOD, the user can write methods.
The distinction is mostly of historical relevance, being relevant to the time where function names were bound at link time in mainstream systems. In a modern system, Java/C#/Python/Javascript/..., we take for granted that function names are bound at runtime. "Calling a function into a module/object" means having the module resolve the function name, then execute the function. Messaging is an implementation detail, just like vtables are another way to implement this idea.
That's a fundamental difference which allows a whole pile of stuff to happen that otherwise would be either hard or impossible.
Messaging versus parameter passing is an entirely different beast that for instance (but not limited to) allows you to send the message to another process for handling and that other process could (theoretically) live on another host.
Smalltalk has a lot of nifty stuff under the hood and never managed to realize even a fraction of its potential due to all kinds of factors none of which have anything to do with the core ideas of the language.
Message passing is a game changer, and is in no way equivalent or comparable to calling functions with a bunch of parameters though implementing that using message passing is trivial.
The runtime binding aspect is only one detail, and not the most important one at that.
- messages are asynchronous, function calls are (pretty much by definition) synchronous
- messages do not require a reply (but function calls typically do expect a result)
- messages are a lot more secure in that the credentials of the sender can be inspected by the recipient which can be walled off in a different area of memory or even on a different machine
- message based architectures are easier to scale than function call based architectures
- message based architectures tend to have higher overhead per message than a function call with the same payload and return value
- message based architectures tend to lend themselves to soft real time approaches better than monolithic threaded software using functions as the main means of passing data around
- message based architectures tend to be more reliable
- message based architectures typically allow for things that are next to impossible in a function based environment, such as process migration and load balancing as well as coping with hardware failures
- message based architectures are easier to debug (by far) than large systems built up using nothing but function calls.
- message based systems tend to be a bit slower, especially if they take the message idea all the way to the lowest levels of the language (such as in smalltalk).
Really, the differences are legion. I've built some stuff using message passing that I would simply have had absolutely no way of getting production ready if not for using a message based approach, it allows you to limit the scope to the processing of a single message at all time rather than to have to model the entire call stack in your head to keep track of what you're doing and why. As an abstraction model it is extremely powerful and for that reason alone I'd happily advise anybody that has not yet played with an environment like that to give it a whirl.
It's not a passe-partout but it certainly is a powerful tool in the toolbox.
> message based architectures are easier to debug (by far)
This is the opposite of my experience, mainly because of the lack of proper call stacks, it is very hard to inspect the reason the values in a message are what they are. Can you elaborate?
That's interesting. I guess this boils down to how far from the point of origin a bug originates.
In a message based system that should be at most one step away from where you find the bad value. If it is more than one step away that's more of an architectural issue, it probably means that you are sending your messages first to some kind of hub which then passes it on to the actual recipient so you lose the point of origin information.
A message that gets passed on untransformed from a module that does not realize the data is corrupted is a missed chance for an integrity check and a subsequent crash.
Smalltalk does a half decent job at integrating the 'debugger' with the runtime environment (they're so tightly integrated it's hard to tell where one starts and the other one ends) and allows you to see fairly detailed traces of what happened to which parameter along the way, it's as detailed as any call stack dump.
Erlang has a 'let it crash' policy (see Joe Armstrong's excellent paper) which tries to enforce the point of origin and the subsequent crash happen as close to each other as possible (and provides a whole range of mechanisms to deal with the fall-out).
A 30 level call-stack like say Java would produce simply doesn't happen in those environments. Though if you programmed in a 'Java' style in say 'erlang' then you could generate one and it would make your life much harder.
No hehe, my experience is mostly C++ PC and console games, so a) let it crash is not viable, and b) performance precludes performing extensive validation (a debuggable but unresponsive game is not viable).
In games, messaging troubles are usually related to systems like character behaviour and AI that deal with multiple complex entities responding to the world state and interacting with each other. It's rarely a case of corrupt data and null pointers (those are easier): physically valid yet logically incorrect or unexpected data.
Message passing as a construct allows you to model each and every object as an independent process which is for many reasons a very powerful tool.
> And what are the most important consequences?
This is where smalltalk got it wrong I think, it never managed to really capitalize on what could have been done with an architecture like that, I suspect this has something to do with the hardware available at the time and the mindset that went with it (machine efficiency at all costs).
We'll likely never really know what we lost, but I suspect that an architecture that is built up out of very small objects each and every one of which works as independently of the others as you can get away with has properties that will be hard if not impossible to simulate in a regular way. Taking that all the way down to the hardware level would allow you to take advantage of architectures like 'fleet'.
Anyway, no way to turn back the clock on that one, I don't think we'll ever see a sudden mainstream resurgence in the interest in these weird but beautiful experiments from the history of computing unless there is a compelling business advantage to be had. It's a bit like the piston gasoline engine, once you've invested untold billions in its development it is very hard to do a do-over even if you suspect that there must be a better alternative unless there is a very large external factor pushing you.
> Let me venture a guess: could it be that Erlang realised this idea of message passing better than Smaltalk ever did?
Erlang does message passing and does it in a very good way, at a level of abstraction where it really moves the needle rather than by defining even language primitives and very basic operations as message passing based. It's extremely practical and shows some fairly significant advantages over more traditional approaches even though the initial deployment will likely come at a significant cost.
Smalltalk does it all the way down to the metal if it has to, which I'm not sure is equally effective in a larger setting.
Messaging and vtables are not at all the same thing. See my other comment here for an explanation of the messaging part.
Vtables by themselves fall just on the other side of the "is it still a message" divide. You can obviously optimize a messaging system further with vtables and enrich a vtable-based system so that it has enough information to be considered "messaging", but out of the box that's where the divide is.
The implication of "function call" is that we're calling a particular function (eg. in `square(x)`, `square` is a function).
The implication of "message passing" is that we're only providing a name (eg. in `square(x)`, `square` is a name). This name is used to look up a function just-in-time, and the mapping may be altered arbitrarily during the course of the program.
To me, it's like a half-way-house between first-order (functions and variables are distinct) and higher-order programming (functions are a sub-set of variables).
Other differences between message passing and function calls aside, I disagree with this characterization. While many mainstream languages perform static binding of functions, it is not an inherent feature of functions (at least as I personally have come to understand them); the canonical example is Common Lisp's generic functions, which behave in almost exactly the way you describe message passing.
I think no small part of the disagreements in this thread can be attributed to people simply having different notions of what phrases like "function call" mean, with some envisioning invoking a statically resolved, monotyped C-style subroutine, and others a more abstract notion of issuing a command to perform a computation with an understood set of semantics on a provided collection of data. The latter is what I think of a function as in a computational context, and from what I've gathered from the other comments here, a fair approximation of what is typically meant by "message passing."
Now, there's something to be said for differences like the treatment of synchronization and process boundaries, but I'm inclined to categorize those as the same sort of pragmatic-but-technically-untrue distinctions as that between so-called "compiled languages" and "interpreted languages." Certainly there is at the very least an isomorphism to be established between the two conventions, given access to threads, promises, and some form of IPC.
Guesstimate: Message passing implies a fully self-encompassed message carrying enough meaning for potential distribution between systems? [Substitute: Erlang VMs, physical machines, or whatever] Function call typically means within the same interpreter instance / language virtual machine / operating system native process.
(ie. in cases where parallelism exists or is warranted, a message passing model enables it. In cases where it does not, they are essentially the same, possibly message passing being slightly slower due to marshalling/encode/decode overheads. These days that overhead is usually irrelevant and thus message-orientation is a plus, as it encourages smaller components and thereby helps to bring clearer architectural paradigms to complex systems)
Message passing in KO (Kay Objects) is always inherently concurrent. After all the object abstraction is "individual computers all the way down"[1], and with separate computers, I hope we agree to call the message asynchronous.
Anyway, mapping the passing of messages to synchronous lookup of a method in Smalltalk was always an optimization purely out of expediency. Highly important at the time so they could get real systems running on their "Interim Dynabooks" (aka Altos, 128K DRAM, 5.8 MHz[2]), and try them out.
However, the ability to do a full message-send was always retained in systems like Smalltalk and descendants such as Objective-C and Ruby, with the DNU hook turning the message that only exists in pieces on the call stack into a bona-fide object (Message in Smalltalk, NSInvocation in Objective-C).
And lo and behold, these hooks were and are used to implement asynchronous, remote and other forms of messaging.
So to answer your question: yes, what Ruby, Smalltalk and Objective-C do is message passing. It is probably on the extreme end of what can still be considered KO-style message passing, which to me means it is late-bound and an object representing the message for further processing can be recovered easily.
These were the twilight days where C and assembly were the only language Real Programmers used, Perl and Tcl or any scripting/interpreted language were considered toys for sysops and never meant for production, OO was treated with deep suspicion, and functional languages were considered crazy talk. I remember people in my company saying a particular C++ replacement program my colleague wrote was "too OO" and would execute 10x slower than required (when it was 2x faster than the legacy).
Point is, a lot of what Kay says is obvious now, but it wasn't in the 90s, and it REALLY wasn't in the 70s.
One sees new things every time one reads this. For example, Kay comes out as a supporter (of sorts) to functional programming principles by calling assignments (mutable state) a metalevel change.