Hacker News new | past | comments | ask | show | jobs | submit login

In the interests of rigor I'm going to propose a ranking system. A higher rank means more "Message Oriented".

Rank 1: Polymorphism. You can "send" a message to an object. You don't know or care exactly what the object does. Other than the polymorphism, the "message" behaves just like a function call. It will definitely be handled by your target object, and it will definitely return instantly with a response. The compiler might even be able to inline this function call.

As seen in: Pretty much every OOP language.

Rank 2: Messages as first-class values. It's possible to write a function that forwards all incoming messages to another object, based on some logic. The caller does not know what will happen to the message. However, the message can be expected to be handled instantly, and it may respond instantly (if it does have a response).

As seen in: Objective-C, Smalltalk

Rank 3: Asynchronous messaging. When sending an outgoing message, it might be placed in a queue and handled at a later date. The message does not return a result instantly, so agents must have some other mechanism (such as including a "reply to" address) to be able to talk back and forth. From the perspective of the caller, it's a bit like putting a message in a bottle and watching it drift off to sea.

As seen in: Erlang, message queueing libraries.

I'm going to be the token annoyingly overeager Go supporter and post that Go channels fit nicely into rank 3.

Scala with actors fit into rank 3 too.

That's nice and simple. I like it. "Message passing" means too many different things (though not as many as "object-oriented"). Distinctions are helpful.

One thing people usually understand as part of "message passing" is "no shared state". How/where do you fit that in?

> One thing people usually understand as part of "message passing" is "no shared state". How/where do you fit that in?

Good question, it seems like all definitions of message passing involve not sharing state. Even OOP emphasizes data hiding as a core practice.

But there's a huge difference between working in a model where you can do it (even if you're "not supposed to") and working in a model where it's impossible. I've been writing a Chrome extension, where the programming model is a #3 in your system — everything is async and shared-nothing — and boy do I notice the difference. Calls between two Chrome contexts are done by async message-passing using JSON, and this is like walking through mud in heavy boots compared to calling something in the same context (a normal function call). No doubt well-designed languages and libraries help to make it easier, but still. I suspect asynchronousness is responsible for more of the mental burden than shared-nothingness is (or, more precisely, serialize-everything-into-JSON-ness), but they're definitely both contributors. It's the latter that makes it impossible for me to pass a DOM element from one context to another, for example.

When "passing a message" is a heavier operation syntactically than "calling a function" is, one thinks of the two as being more fundamentally different than they really are. I find that interesting because it's superficial yet influential.

Do Erlang programmers think of function calls as a kind of message passing? or as totally different things? How far apart are the two, psychologically? Is sync vs. async the more fundamental distinction?

About Erlang and function calls: I tend to view them differently. For me, in Erlang code, function call is about 'here', while sending a message is about 'there'.

Syntactic difference is big, but neither function call nor message send are heavy:

    func({my, params, in, a tuple})
    proc ! {my, params, in, a tuple}
The difference is that function call returns something, message send just happens and we have no guarantee at all what is going to happen with the message.

We frequently write functions which send message and wait for reply for a certain amount of time, and then return this reply or error if no reply was sent.

So, in Erlang there is - I think - huge difference between functions and messages, but that is because it's functions are just that: functions.

The situation is quite different in Smalltalk, where you have no functions at all, only objects. Everything there already is a message being passed, and so the difference between asynchronous and synchronous message should be very minor. I don't know Smalltalk well enough, but I suspect there are libraries implementing async message passing in Smalltalk and that they are almost indistinguishable from normal Smalltalk code, if at all.

> Everything [in Smalltalk] already is a message being passed, and so the difference between asynchronous and synchronous message should be very minor

But this seems to confuse paulhodge's #2 and #3. Messages as first-class values don't give you asynchronous messaging for free. Or do you just mean that syntactically the async code would look like normal sync code? That's true of JS as well, of course, and it's a problem. Async vs. sync is a fundamental distinction for the programmer, and one wants help to keep track of what's going on. I suspect Erlang got it very right in having two lightweight but distinct notations for these two deeply distinct things. What other languages do that?

Edit: It occurred to me that you said something profound:

> For me, in Erlang code, function call is about 'here', while sending a message is about 'there'.

"Here" vs. "there" is a spatial distinction; "sync" vs. "async" is a temporal one. The temporal distinction is far harder to get one's (my) head around, perhaps because code itself is laid out spatially, so you constantly have to recreate its temporal model in your head when you read it, and that's taxing. It's far easier to think of code as divided up spatially. We build up a system by making repeated 'inner' vs 'outer' distinctions - i.e. modularity. That structure is static while the temporal structure is dynamic and unpredictable. To write good async code you have to get in the habit of thinking "who knows when". But you never have to think "who knows where". The code tells you where.

This makes me think that there is wisdom in Erlang's decision to identify the temporal distinction (sync vs. async) with a spatial one (inside a process vs. outside it) — thus guaranteeing a translation from the harder mental category to the easier one — and to provide distinct notations for there-and-async vs. here-and-sync, so you always know which is which. Total complexity seems greatly reduced this way compared to a model in which the two distinctions are orthogonal and can combine in arbitrary ways.

For Erlang to say that between objects (i.e. processes) things are async and pass messages, while within an object they are sync and call functions, is a major departure from the Smalltalkian "everything is the same everywhere" small-and-regular philosophy. I'm fond of small-and-regular designs, but sync vs. async is one of those distinctions that is so fundamental, it's folly not to have it in the core unless you intend to get away with pretending that everything is always synchronous.

There is one more aspect here: concurrent vs. sequential. You can have a system which is both asynchronous and sequential, like JavaScript and Twisted, and another which is concurrent, but somewhat sequential (with mutexes and such). Erlang has both asynchronous message passing and concurrent threads of control (without any locks) which, without the simplification it makes (there -> async, concurrent; here -> sync, sequential and no shared state at all) would be pure madness :))

So you're saying that the alternative to Erlang's simplification is not a four-way combination but an 8-way one. Yikes.

So would messages ala Laurent Bungion's (sp?) MVVM-Light Messaging system fall under 1,2, or 3? You send out a message from an object and then any object "registered" to receive these messages can pick them up and handle them as they wish... doing something, doing nothing.... returning a value or more likely not...

Can't most languages in (1) implement forwardable patterns that accomplish (2), or is there some underlying language features in Objective-C or Smalltalk that are superior in this case?

Sure, with enough boilerplate code you can do anything in anything. It's more of a categorization of what's the normal style, of what is already provided and encouraged.

In Objective-C, I can easily say "if anyone calls a method on me that I don't implement, sent it to this object instead". I can also say "if anyone calls a method on me that I don't implement, do (foo) instead".

C++, for example, has no equivalent language facility for these scenarios; in fact they're nonsensical since they would both fail to compile.

You could build a similar system for C++ by making every object inherit from some base class that has a method performMethod( method, params) and replace all C++ method calls with your modified calls; but you're building a facility from scratch that comes out-of-the-box on Objective-C.

I'm guessing that in these languages you don't need special argument types (or you can use the same code for any function)

There are several others that support this though... Python and JavaScript spring to mind.

Asynchronous messages can also return results instantly in the form of futures/promises, avoiding the need to program in a continuation passing style with "reply-to" addresses. Mark Miller's E language does this.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact