If you send a message to something that's supposed to do a task, and it just ignores your message because it was the wrong type or whatever, how are you supposed to debug that, or reason about the correctness of your program at all?
If you send a message to something that's supposed to do a task, and it just ignores your message because it was the wrong type or whatever, how are you supposed to debug that, or reason about the correctness of your program at all?
You can reason about your protocols, as they have an algorithm. But your object graph as an entire entity is a bunch of black boxes with very few observable properties about which to reason, or indeed even need to in the world of isolation.
You can't statically typecheck the internet. Oh, and no-one said the failure needs to be silent.
I didn't feel the author got the messaging part. 'You send some data to an object' kind of misses the point.
Going back to Kay's inspiration in cellular function the messaging is more a broadcast, though proximity matters as far as which other cell responds (Internet routing tables are a good approximation but you need something more dynamic to allow objects to be created and destroyed).
Alan Kay and fellow minded et al. are looking at Biology and to an extent Physics as the successful approach to pervasive computation at large.
It is the desirable feature set of the biological organism that informs that perspective: self-healing, self-regulating, regenerative systems operating in an open-ended operational context.
So that is the holy grail. Or at least one of them. Clearly biological systems can do all that. Can we do this with software? That's what Alan Kay wants us to find out.
What we're seeing in "successful" biological systems is survivorship bias. They are useful because the system they operate in happens to be such that their function is useful to the system (they are in a temporary equilibrium). And that system is not under anyone's control, nor can it be, because every tiny change in one component, or the environment they operate in, alters that equilibrium in ways we can't predict. But there's no controlling entity pushing biology towards a particular function, so this doesn't matter in nature.
A computer system modeled after this would be by nature difficult to fathom and brittle to change.
Run time checking is not unreal. Internet run on that. Ethernet run on that.
Too much error correction like sna and token ring make the network brittle.
You think you help error handling but by enforcing failure you have not decouple the “objects”. By assuming type and minor change the whole thing collapse on you.
It is between ibm network and internet. Or osi vs ietf.
People forget this, particularly the ones who claim "this can't possibly work". If that's your position, you are simply wrong. Now which works better in what ways is something we can talk about.
> Internet
This is another big one people seem to not think about. Kay was thinking about scaling up, or rather about taking an idea that can scale up and then scaling it down. The internet and the WWW clearly demonstrate that you must late-bind. In fact, Allen Wirfs-Brock noted how the Internet is a lot like a huge distributed Smalltalk image: it runs all the time, it's late-bound, it's very stateful and you can't take it down and restart it from scratch.
Isn't always possible, but it's worth the constant vigilance to drive each feature towards that type of architecture.
Let's say you want to take payment and ship a product. You send a message that payment of X is needed. And... nothing happens because the component that handles that isn't listening for whatever reason. Do you wait for a message saying payment was posted? How long do you wait? What about the UI? How long does the customer wait?
It seems like a messaging system like this would require all calling components to micro-manage anything they're calling, to make sure things actually get done...
I dislike it for modelling RPC which is what you seem to be referring to.
So if I don't hear from the payment that it has been initiated, then I have to go on the basis that I can't get a payment.
But I also don't get to tell the payment how to deal with its internal activities, I don't want to know about it retrying, or working out which payment provider to use etc.
Objects arent FiniteStateMachineManagerFactories. They're actual things that your system is built to handle. If an object needs an FSM, it'll get one, just like cells have mitochondria.
I think that's part of the trick, actually. When every component of a system behaves in similar ways, it's easier to extract that behavior into re-usable patterns.
Rather than each component worrying about handling failure and micromanagement, that job is delegated to an another component, a supervisor or orchestrator. Supervision trees, automatic service discovery, and elastic resource managers all come to mind.
What payment systems don't send messages to communicate with the credit card company, or the eCheck service, or Venmo/PayPal/Apple pay? Websites and native apps alike have spinning graphics and loading bars, messages that say "please don't refresh the page while we do this". These systems are fundamentally asynchronous to begin with. By uniformly applying the practices you get consistency, in exchange for a higher low-bar of complexity (which you're likely to exceed anyway).
Sometimes just ignoring certain messages is fine: You might slot in an object as a sink to log certain messages and not care about others. Other times you want to be able to do things like delegate certain subsets of messages to other objects without having to know the precise set of messages in the sender or ultimate recipient want to exchange.
Returning errors by default is reasonable, as long as you can override that when it makes sense.
[1] As opposed to its components (infrastructure, protocols, individual websites), which are designed and maintained, and which would in general be a nightmare to design and maintain if they followed the principles described by vidarh.
But taking it a point further: A huge amount of large websites are built on languages with this level of dynamic features, and often take advantage of it heavily. Every site that uses Rails for example (or any Ruby framework) will likely heavily rely on runtime introspection of the database to dynamically generate parts of the ORM apis.
I'm right now working on a Ruby application where I went a step further and axed a large chunk of semi-manual API writing by writing a few small components that dynamically generates most of the API at runtime from small pieces of metadata and the actual state of the database. A large chunk of the userinterface is similarly dynamically generated from data dynamically generated by the dynamically generated backend API.
There is no way we could deliver what we deliver with the kind of size team we're using if we could not rely on introspection and dynamic features like this to generate most things.
I think you're picturing just losing messages, but that is not what happens unless you explicitly design a system to randomly drop messages. The reality is a system where by default you'll typically still get exceptions if you call something that does not exist (and in production use, the stack traces from those exceptions likely ends up somewhere like Bugsnag), but where you can selectively override what happens and e.g. redirect messages somewhere else. If someone sends a message that truly can not be handled, we'll still throw exceptions.
But that flexibility means you save a massive amount of boilerplate. When I switched from C++ to Ruby, that was a major driver - I'd spent years doing meta-programming using templates, and was dimissive of dynamic typing. Until I tried it and realized that for the pitfalls it has, the amount of ceremony and boilerplate that was cut was astounding.
I write all my code in a homegrown editor these days. It uses Drb to split the UI frontend with a server that holds all open buffers. Drb is a Ruby RPC library that uses the ability to intercept all messages to dynamically create proxy objects that serialize calls over the network connection. Because we can do that, it's almost entirely transparent and no custom code whatsoever needs to be written.
If the server side throws an exception, by client catches it and throws me into a REPL (or it could have dumped state and exited if I preferred). Messages don't get randomly lost, even though the client side objects have no idea what the actual interfaces of the server side objects are.
In a typical Simula I program (C++ Java PHP C# etc) if you fatal in your recipient's object your callee and the entire program is going to come crashing down, in something like erlang the callee can keep going
Before jumping to putting everything on a queue, there are downsides like you've identified which is why there's this comment from Rich Hickey on the erlang variant of Clojure: https://github.com/clojerl/clojerl#but-but-rich-hickey-lists...
Essentially, it's good when you need it but an overhead when you don't, kind of like async in my mind, use it when you absolutely need it, but otherwise, you'd be better operating without it
It's not that it isn't great when you do need it, but there are also plenty of cases where you don't need it and it's just unnecessary overhead.