Hacker News new | past | comments | ask | show | jobs | submit login
A world of message-oriented programming languages (2018) (conman.org)
74 points by bibyte 36 days ago | hide | past | web | favorite | 25 comments



I've read CALLG/CALLS VAX example, and became curious -- why waste microcode for two types of function calls, isn't that hard? Isn't "initalizing a temporary structure" expensive? I found the nice explanation [0] and apparently no, it was pretty simple.

- A parameter list is a series of words in adjacent memory cells. The AP register points to the first of these. The first word holds a COUNT of the number of parameters. The remaining words hold the individual parameters.

- CALLG copies passed address to AP register. It must contain parameter list in the right format.

- CALLS simply pushes the parameter (argument count) to the stack. Since the stack grows toward low addresses, the stack ends up with exactly right sequence of words to represent a parameter list! So the instruction just copies stack pointer to AP, and calls the function. It also sets a special flag to pop values from stack on return.

This is a very neat approach -- with a bit more than a extra push, VAX got a nice instruction to optimize common case of function calls.

Unfortunately, this approach would not work with modern CPUs. In modern PCs, memory accesses are much slower that register accesses, so first few parameters are passed in registers. Calling function with multiple parameters is very different from calling it with one struct.

[0] http://www.math-cs.gordon.edu/courses/cs222/lectures/procedu...


VAX was designed in a time where it was common to write assembly by hand, and so programmer ergonomics was an important consideration. Instructions were introduced where today we would add a library function - this peaked with POLY [1] I think?

Nowadays ISAs are compiler targets and nothing more, so human-ergonomic instructions are hilariously quaint.

1: http://uranium.vaxpower.org/~isildur/vax/week.html


Even OO languages can be message passing. Ruby method calls are syntactic sugar over the send method of Kernel, which basically is the interpreter itself. Examples at https://stackoverflow.com/questions/3337285/what-does-send-d...

And Smalltalk explicitly calls "message" what is sent to the destination object https://stackoverflow.com/questions/42498438/whats-so-specia...


> Even OO languages can be message passing.

IIRC, Kay said message passing was the essential feature of his conception of OOP in the first place, which was subsequently ignored by most—so 'even OO languages' may be slightly misleading ;)

(Someone please correct me if I'm mistaken about this.)


You are not wrong. Although Kay described what he thought of as the essential feature(s) of OO in slightly different ways, "messaging" was usually in there.

> Smalltalk is not only NOT its syntax or the class library, it is not even about classes. I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea.

> The big idea is "messaging"

--http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-...

> I thought of objects being like biological cells and/or individual computers on a network, only able to communicate with messages...

> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

--http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay...

(I wasn't sure what he meant by "extreme late-binding", but Wikipedia says:

> Late binding, dynamic binding[1], or dynamic linkage[2] is a computer programming mechanism in which the method being called upon an object or the function being called with arguments is looked up by name at runtime.

--https://en.wikipedia.org/wiki/Late_binding

That makes sense to me as part of transforming 'method calling' into 'message passing' -- the 'method name' you are sending isn't actually fixed to a particular implementation that will receive it, until it is sent. Rubyists may be familiar with this, as ruby indeed implements methods as 'message passing', just like this.)


You'll probably love to read this interview

https://www.infoq.com/interviews/johnson-armstrong-oop

I quote from the first answer of Armstrong (Erlang)

> The 3 things that object oriented programming has it's messaging, which is possibly the most important thing. The next thing is isolation and that's what I talked about earlier, that my program shouldn't crash your program, if the 2 things are isolated, then any mistakes I make in my program will not crash your program. [...]

> The third thing you want is polymorphism. Polymorphism is especially regarding messaging, that's just there for the programmer's convenience. It's very nice to have for all objects or all processes or whatever you call them, to have a printMe method - "Go print yourself" and then they print themselves. [...]

> Erlang has got all these things. It's got isolation, it's got polymorphism and it's got pure messaging. From that point of view, we might say it's the only object oriented language [...]

By not crashing the other program he means two programs running inside the same VM. There can be many programs running inside the same BEAM (Erlang's VM) and they can be kept isolated via different supervisors. It's the normal state of that system. Example: what in other languages is a library to make HTTP calls in Erlang (or Elixir) is a separate process that receives messages with the arguments of the calls.


"extreme late-binding"

Perl allows you to do:

$object->$method($arg);

And a special can() method to see if $method exists...returns a usable code reference.

There's even a pretty nutty wildcard way of handling methods/functions that don't exist, as if they do (AUTOLOAD): https://perldoc.perl.org/perlsub.html#Autoloading

Though I've never run into a plausible reason to do any of that.


> Ruby method calls are syntactic sugar over the send method of Kernel, which basically is the interpreter itself.

Technically, the method you are thinking about (for modern Ruby) is __send__; send is (to support legacy code) by default an alias for __send__ but can be overridden without breaking the world (communication-related classes do this sometimes, because send is often a natural name for an action in their domain.)

Redefining __send__ in a class is possible, but quite likely to blow up in interesting ways.


It's funny how the language you primarily work with changes your perspective.

JavaScript supports "object spreading", which lets you do this:

  let foo = { x: 1, y: 2 };
  ...
  let { x, y } = foo;
  console.log(x + y); // 3
A common pattern is to use this as a stand-in for named/unordered function arguments:

  function bar({ x, y }) {
    return x + y;
  }

  bar({ x: 3, y: 4 });

  // or...
  let foo = { x: 1, y: 2 };
  bar(foo);
Which is pretty much exactly the "epiphany" at the bottom (the interchangeability of these two). I don't mean to diminish the author's thought process; I just think it's fascinating how different programming languages (even ones so similar in syntax) dramatically shape the way one thinks about a given problem.


That's actually object destructuring. Object spreading is where you “spread“ an object's keys and values into a new object literal:

   const foo = { x: 1, y: 2 };
   const bar = { ...foo, z: 3 }; // has x, y, and z


Whoops. Oh well


It's not relevant. I really only corrected you in case someone wants to google it.


Speaking of language perspective. I see obj spread in JS for arguments as an ugly hack to make up for the lack of named and optional args. It gets really ugly when you attempt to set defaults.

My perspective is from Python, which I think gets it very right.


But named-args in Python are also syntactic sugar for passing dictionaries around. Object-unpacking of the the kind described here is another one.


Syntactic sugar is the important part here.

You can always pass an object and handle defaults and default merging within the function body, sure.

I use JavaScript a ton and I do love object spread and array destructuring. But writing function signatures is still very clumsy.


There is optional args for JS.

And it's not hack since it's a partial form of pattern matching.

Although simple and consice is absolutely not JS is famous for, a lot of functional programming languages with consice design do the same thing.


Function calls are a special case of messages.

They are synchronous. Messages can be synchronous or asynchronous.

Reification and distribution are natural for messages. For function calls...not so much. Procedure calls, a little closer -> RPCs, OK, but still not really natural, as we have discovered over time.

One big one is that you can actively wait for/receive messages. I am not sure how to map that onto a function or procedure call at all (not: "implement").

Dynamic/late binding again is natural with messages, whereas I am not sure a function call with late binding is still reasonably a function call. Some would say that the fact that the receiver decides how to interpret a message is "declarative", and they have a point, though I am not sure I agree 100%.

Conversely: message sends are the generalisation of function calls.


> Reification and distribution are natural for messages. For function calls...not so much.

If you have a way to call functions/methods dynamically by name, and a way to store and pass tuples of arguments when doing so, reifying function/method calls is natural, too.


Hmm...sounds like a message-send to me?


Any object with methods is always waiting on messages. Those methods represent pattern matches on any input message.


Yep. Conceptually, synchronous message-send is a special-case of asynchronous message-send. Implementation-wise, it's often useful to model it the other way around.


Nice article! In order to "pass messages" you have to have two things and one has to send a message to another - or one thing passing messages to itself. So if you have a function and it reads in data off the program stack from another function that is a form of message passing. I guess anything can be seen as message passing. Today however, when I think of message passing it usually in the contexts of threads or otherwise sequential processes communicating and not a single process sending messages to different segments of program code - and yet, as the article states, that is a perfectly legit way to think about it.


TL;DR:

"Calling a function with parameters is just another form of synchronous message passing, either by-reference or by-value [...] But yes, we already have message-oriented programming languages—if you squint the right way"


Reification is a nice feature of messages -- you can capture the "idea" of a single function call as a message, so it can be logged, forwarded to another component through the network, etc.


Which is part of why the Redux patterns became so common in React apps, because that message reification for logging and debugging is hugely useful.

That also strikes to the heart of a lot of complaints about Redux patterns and their boiler-plate in that a lot of the raw details of that reification are left to the developer (in the various action-creator patterns), as well as the message bus dispatch (the various reducer patterns). There are usually questions about why more of it isn't "just" single function calls.




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

Search: