Hacker News new | past | comments | ask | show | jobs | submit login
“What Alan Kay Got Wrong About Objects” (metaobject.com)
241 points by mpweiher on Nov 17, 2019 | hide | past | favorite | 231 comments



Kay was at least as inspired by biological processes -- bacteria transmitting chemical signals to one another, for instance -- as he was by computer networks.

I think the big thing that Kay missed was asynchronicity. What Kay was thinking of when he coined the term "object oriented" -- rather than C++ -- was a sort of synchronous actor model in which the actor would always wait for a response after sending a message. This maps nicely onto function calls, but considerably more power can be had by letting the actor optionally continue doing stuff while awaiting a response. Bacteria continue to metabolize after sending chemical signals to their brethren after all!

As I've seen it said, Erlang is Smalltalk done right.


This whole subthread about asynchronous objects, and the actor model, made me think of "background" processing where objects have a running life of their own, and it reminded me of a recent HN comment about almost the opposite approach, where nothing is allowed to be spawned off in the "background".

The author contends that Go's "go" (and indeed async blocks in a number of languages) which launch new concurrent objects which the parent block doesn't have to wait for, are a form of badly structured programming.

  https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
  https://news.ycombinator.com/item?id=21537706
The structured concurrency described there reminds me of "par" blocks in some languages (Occam, Handel-C, XMOS XC), where each statement inside the block runs in parallel and the whole block waits until all statements inside have completed.

They do have asynchronous request/response patterns, but things can't be started and left to carry on running how they want, like you might think of bacteria.


The "structured concurrency" pattern, simply amounts to starting some async jobs (concurrent or parallel) and collecting all of them at the end of some context.

One antidote to unstructured concurrency is to use something like trio in order to make async job spawning a function of a context manager (called nursery in trio) so all job results will be collected when the nursery block ends.


I believe the structured concurrency article is written by the author of Trio (I could be wrong.)


If by "done right" you mean "the working programmer's smalltalk". Erlang and, probably even more by philosophy, Elixir, pushes you towards Kay's model, but the underlying BEAM gives you enough "cheats" to let you be incredibly productive. Especially in elixir those cheats are sufficiently ugly (as in code esthetics, nothing more) that you don't really want to use them unless you must, and when you do use them it's reasonably good practice wrap it in a macro so that the implementation details can be hidden away. In some cases where your using a non-kayish cheat (like it only helps to make your test parallelize over stateful services) you can make your macro no-op in prod.


The Actor Model has been used in many important large applications [Bonér and Reisz 2017, Cesarini 2019]. In order to implement next generation Reusable Scalable Intelligent Systems [Hewitt 2019], the following extensions to Erlang are needed:

* Automatically reclamation of storage of an unreachable future [Baker and Hewitt 1977] (e.g. process in Erlang). For example, an Erlang process can be orphaned if its Process Identifier becomes inaccessible.

* Language support for holes in the region of mutual exclusion of an Actor implementation. For example, in Erlang it is necessary for application programmers to explicitly code a message handler for each re-entry into the region of mutual exclusion potentially enabling cyberattacks from outside the process.

* Automatic cancellation of requests that have taken too long. For example, in Erlang it is necessary for application programmers to explicitly code a time-out for each message send that might take too long.


> an Erlang process can be orphaned if its Process Identifier becomes inaccessible.

Has this ever actually happened? Surely with as many deploys as erlang has, it would have been observed. A casual google search of "orphaned erlang process" shows the parent comment in the top four results, and no other results that mention orphaned erlang processes.

My point about erlang is that it's great precisely because, like all great systems, it doesn't dogmatically adhere to the academic design, and rather makes compromises and is refined by practitioners.

> re-entry into the region of mutual exclusion potentially enabling cyberattacks from outside the process

Typically you run your erlang actors inside of a trusted system. The only way to give untrusted actors access to your system, is explicitly (so no one does it). There are warning signs all over the place around every place where that might happen (for example distributed erlang) and in prod you really really shouldn't deploy distributed erlang without tons of security in depth in a deeply buried vLAN, or, TLS (instructions provided in the standard libs).

The only realistic vectors of attacks on your actor system is by compromising the entry points, like the actors which live to process your TCP or TLS entry points, but you can bet that ericcson has taken a lot of effort to harden those systems and, in fact, the ericcson SSL system was not vulnerable to the Heartbleed attack (despite using OpenSSL) because there are some very smart engineers at ericcson, and also the language generally doesn't have buffer overflows.

BTW, thanks for coming to my stanford ee380 talk a couple of Februaries ago -- unfortunately I wasn't aware of what you looked like, so I didn't get that you were in the audience at the time.


Erlang's insecurities will become more of a problem in the implementation of Reusable Scalable Intelligent Systems. See the following:

    https://papers.ssrn.com/abstract=3428114
PS. You are very welcome. Thanks for your EE380 talk!


> For example, an Erlang process can be orphaned if its Process Identifier becomes inaccessible.

Determining this property of a program seems to me to be about as hard as the halting problem, since (1) an Erlang process can send its Pid to another process in a message, and (2) the VM/compiler can't tell a priori whether an erlang process will access its own Pid, or even send a message, in the future. I haven't read the paper you linked though, so I'm sure they found a way around this.


We need a strongly typed version of Erlang so that garbage collector can tell when a future (i.e. process) inaccessible.


It wasn't strongly typed exactly, but Microsoft's DCOM protocol featured a simple distributed garbage collector that would automatically unref objects and allow them to be deallocated in some cases. DCOM allowed processes to send handles to (relatively for the time) strongly typed interfaces for objects across the network, in a form of PID that was recognised on a LAN and could be resolved back to the original object in the original process. It was called an OXID (object exporter ID), if I recall correctly.

It's not well known but what we now call the actor model was pretty widespread in Windows programming in the 1990s. Partly because Visual Basic was like JavaScript: single threaded runtime, no shared memory. To aid developers in exploiting parallelism and using blocking APIs you could create parallel "workers" to use JS terminology (apartments in MS speak), but Windows gave you object oriented RPC between the workers. So it was an RPC layer on top of message passing. You could do asynchronous RPCs if you wanted to:

https://docs.microsoft.com/en-us/windows/win32/rpc/asynchron...

This was used to great effect in rather mundane programs like InstallShield, where the GUI ran in one worker and the install engine ran in an isolated (but in process) world, with the two communicating using RPCs/typed message passing.


That's what Pony was trying to do, but sadly it's lost most (all by now?) of its momentum.


Yeah I do still check in with Pony from time to time and it is still puffin along but it has lost most of its momentum. Imo it went to far in the classes direction for its objects and started to have a too complicated syntax, while still missing making some parts clear and easy to get that imo needs to be very clear in the syntax of a high performant language. Things like if a thing you create needs to be allocated or not. I have several times thought about implementing something Pony / actor model -like in zig or something, but without all the classes stuff.


Do you feel it's possible to achieve that while retaining the hot code loading mechanisms and flexible message handling that allows distributed systems to process versioned, legacy messages?

Thanks for contributing to the thread, Professor Hewitt.


Should be possible to achieve your goals. Researchers are working on prototypes.

You are very welcome!


As a fan of Elixir, I'd love to hear Jose Valim, Chris McCord or any of the other core devs to chime in on this subthread!


I believe you're mistaken about this one:

> Automatic cancellation of requests that have taken too long. For example, in Erlang it is necessary for application programmers to explicitly code a time-out for each message send that might take too long.

https://hexdocs.pm/elixir/GenServer.html#call/3

> timeout is an integer greater than zero which specifies how many milliseconds to wait for a reply, or the atom :infinity to wait indefinitely. The default value is 5000. If no reply is received within the specified time, the function call fails and the caller exits.


That's the gen_server functional interface, not the message send primitive (!).


That's OK. It doesn't matter at what abstraction layer the timeout is taken care of as long as it is taken care of. That it's in a library rather than the language core is an irrelevant distinction.


It’s technically the receive primitive doing this using the after clause. The addition of unique references and selective receive then allows “call” style interactions to built (like gen_server).

Likewise, links and monitors are used for lifetime concerns instead of reference counting actors like Pony or using a global tracing collector. This has some advantages and disadvantages but for the most part I personally find it far more practical than other models which are more like the classic actor model.


Aha, thanks. I guess I'm not clear as to why an actor would care about the egress time of a message (unless it were waiting for a response)


Promise management features are coming to Monte soon, satisfying your first and third bullet points. The second point, of course, comes for free with E-style vats.


What is the precise definition of a "promise"?

I.e., what is the interface of a promise?


Monte is based on E, so they are using E-style promises. An example of ActorScript in Monte is at [0].

There are two main operations that can be done with a promise. We can wait for it to complete, and then perform another action when it is complete, known as "when-blocks"; we can also send a message on top of another sent message, informally known as "promise pipelining".

When-blocks:

    when (action) -> { doMoreStuff() }
Promise pipelining:

    promise<-add(4)<-subtract(3)
[0] https://groups.google.com/forum/#!topic/cap-talk/y1bEqOoeswE


Promises are used most prominently in current-generation JavaScript where they lack a proper interface. Instead promises are standardized using language constructs. Promises have the well-known disadvantage that promises are contagious, i.e, once a promise is used then everything that continues must be promises.

On the other hand, a Future [Baker and Hewitt 1977] has the following well defined interface:

   Future<t> *interface* resolve[] ↦ t


The process id is not the only way to code the sending of a message in Erlang. You also can send a message to a named group if I remember right. Also, if you are using a supervisor tree (and for anything non-trivial you should), then the supervisor retains the process id. If that was not enough, as part of the Erlang runtime there is a process that handles binding and naming lookups, and it has the process id. Really don't see how this could happen.

I consider bullet 2 to be a feature of Erlang, not a bug. If a module implements a behavior you know it implements those functions, there's no checking needed. Yes, it increases your attack surface, but outside apps should not be messaging Erlang processes directly.

Bullet 3 seems like sugar. You can wrap the call in your own code that specifies a default timeout.

I'm not saying your concepts are wrong, they are interesting and a more complete/accurate Actor model implementation in Erlang could be a good thing. However, the points you specifically made about Erlang do not seem to me to be needed adds to the language.


> Bullet 3 seems like sugar. You can wrap the call in your own code that specifies a default timeout.

Which is exactly what GenServer does. You rarely need to directly send a message to an Erlang process - that is usually done under some abstraction providing the properties that you need.


Not sure what apps you refer to in your two citations, one is probably just about Akka and not an app per se. And these two papers were written in the last two years.

There is still no credible evidence that the actors system is better than the more traditional way of writing distributed apps, especially now that more and more languages offer an increasing amount of asynchronous approaches.


I don't think there is a traditional way of writing distributed apps. The whole field is extremely young(/aka immature), and the ways are still being discovered. The actor model is just one way, but it lines up fairly nicely with what Joe Armstrong was doing with Erlang. I think they both deserve high marks.


To see if it is Actor, you have to check if it follows the rules. Akka and Erlang have been used to implement some important scalable systems.

What do you recommend as a "traditional" way to implement scalable systems?


> Akka and Erlang have been used to implement some important scalable systems

Very few, really. Maybe you can come up with a few (besides the Ericsson router), but they are a tiny, tiny minority of how we build scalable systems today, and even if some systems are built this way, it's still no evidence that using Akka or Erlang was the best technology.

> What do you recommend as a "traditional" way to implement scalable systems?

I'm not recommending anything, I'm just observing that a crushing majority of scalable systems in use today, reliably serving billions of people and billions of API calls, are built on traditional, imperative technologies (C, C++, Java, C#, JavaScript, etc...).

This past decade, these services have been supplemented by increasingly powerful threading (thread pool, fork join, work stealing, etc...) and asynchronous (coroutines, promises, futures, channels, etc...) approaches.

The actor model has so far completely failed to prove itself as an improvement over these traditional approaches.

I also happen to think the actor model is terrible to debug, deeply violates encapsulation, and often forces developers to forego static typing, but that's more of a personal opinion than an observation.


> Very few, really. Maybe you can come up with a few (besides the Ericsson router), but they are a tiny, tiny minority of how we build scalable systems today,

Yeah, it's a small fraction because it's a rare language, but it's responsible for an outsized number of highly-scalable systems: WhatsApp, several massive ad companies, several massive trading systems, several large analytics companies. Plus most of the production XMPP systems that have been used at scale.

Look, I'm not even an Erlang/Elixir programmer, but it's pretty clearly a great technology for building scalable, fault-tolerant systems.

> I also happen to think the actor model is terrible to debug, deeply violates encapsulation, and often forces developers to forego static typing, but that's more of a personal opinion than an observation.

I mostly work in statically-typed functional programming languages at work, so I'm sympathetic to this complaint. But Erlang has the concept of fail-fast and fault tolerance that make the lack of type safety not as big of a deal as it normally would be.


It's very hard to know exactly how much of WhatsApp uses Erlang (there is hardly any documentation of that besides vague public release docs), and at the end of the day, like I said, just because a system is written with a technology doesn't mean it was the best idea.

> But Erlang has the concept of fail-fast and fault tolerance that make the lack of type safety not as big of a deal as it normally would be.

That "fail fast" / "let it crash" thing that Erlang brags about has never been convincing to me. First, because you still need to implement all these supervisors yourself anyway so the complexity is still there. Second, it's an admission that because of its lack of static typing, Erlang developers are just throwing their hands in the air and try to make pass what is a fundamental flaw in the language (that you can't effectively and exhaustively catch errors) as a strength.

Besides, Erlang doesn't have the monopoly on "let it crash": plenty of languages have better ways to catch and recover from errors, either through exceptions or algebraic data types that allow the compiler to make sure you have covered all the error cases.


> First, because you still need to implement all these supervisors yourself anyway so the complexity is still there.

Have you implemented an erlang/elixir supervisor? It's not hard. It would probably take me about 60 seconds.

> Besides, Erlang doesn't have the monopoly on "let it crash": plenty of languages have better ways to catch and recover from errors, either through exceptions or algebraic data types that allow the compiler to make sure you have covered all the error cases.

Simply handling the error is not the primary reason for "let it crash". The reason for let it crash is to have the remainder of the system continue to make forward progress and with minimal disruption.

I think if anything the greatest thing about let it crash is that I can have a relatively green junior put something into prod without worrying that an uncaught error will bring down the whole system.


for anyone else wondering what this dude is taking about https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3428114


“this dude” appears to be Carl Hewitt, creator of the actor model


I didn't think that Kay's thinking was exactly the Smalltalk model. It is just that it was one feasible implementation with some key compromises thrown in. I don't think he missed asynchronicity. Just that the machines of his time would've made it next to impossible to be purist about that. He is smart enough to see that the biological systems he was inspired by are asynchronous. In other words, the synchronicity of Smalltalk is somewhat accidental, with good benefits reaped from the idea of reified messages, which Smalltalk does have and C++ etc. don't.

Erlang is indeed Smalltalk done right, but, as the OP wrote on the blog, it is not only about messaging and is about the additional constraints.

We've seen REST principles used within a single computer in the AppleScript system and protocol, for example, where you could think of applications running on a single computer as being individual computers in their own right and you need to be able to manipulate them using messages that talk using representations of objects in these applications. For example, "window 1" could be a representation for the first window in an application according to some internal ordering in it.

We also see REST-like interfaces at various system boundaries - for example, addressing textures on a GPU when preparing instructions on the host CPU. Databases too provide tables as resources and produce/consume representations of these tables to perform their functions. So it isn't surprising to see connectors to APIs exposed via ODBC or REST APIs to RDBMS tables as with PostgREST.

So the "InProcess-REST" is in principle understandable as an application architecture pattern, though with the caveat that I haven't gone into the specifics of the OP's work on this (expensive book, etc.).


"REST principles used within a single computer in the AppleScript system and protocol"

In contrast to DLLs, shared data, COM+, ActiveX, plugins, and so forth.

Terrific observation. Thank you.


In Logic Programming, Actors continue processing. See the following:

https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3428114


One very interesting development in this direction is Web Prolog by Torbjörn Lager:

https://github.com/Web-Prolog/swi-web-prolog/raw/master/book...

Quoting from the introduction:

"In particular, we pick the features that make Erlang into an actor programming language, and on top of these we define the concept of a pengine – a programming abstraction in the form of a special kind of actor which closely mirrors the behaviour of a Prolog top-level."

And quoting from Section 1.3 of the current draft:

"Is the actor programming model compatible with the logic programming model, and does the combination of the two models make sense? Yes, we will try to show that the actor programming model of Web Prolog is able to co-exist harmoniously with its logic programming model, and that both models are usable in the same program, with actors used for modelling the reactive, deterministic interfaces to the user and other external entities, and with the non-deterministic component behaving as an actor, encapsulating the non-determinism."

Chapter 3 cites your work directly:

"Actors, as specified in (Hewitt et al., 1973), involve entities that have their own mailbox."


Unfortunately, Actors cannot be efficiently implemented in browsers because of the way that they implement concurrency.


I’m not sure I agree. For example, nothing about Erlang makes it unsuitable for a browser environment, it can handle running single threaded, or distributed across workers, where each worker runs its own scheduler. The only limitation is interacting with the DOM, since that must all happen in the main thread, but it is still possible to treat that the same way other blocking I/O is dealt with in the scheduler.

I’m actually working on a compiler/runtime for Erlang for WebAssembly, so I’m maybe more familiar than most with how it works in practice.


Workers can be made to work in a browser, but communication can be awkward and slow.


> Workers can be made to work in a browser, but communication can be awkward and slow.

True, but with the SharedMemory browser API, things are looking up (but not yet implemented in all major browsers).


Apologies, Professor, I missed your reply!

SharedArrayBuffer makes communication between schedulers essentially equivalent to non-browser environments, but support for it is still iffy at best due to Spectre. Luckily, that is a temporary problem, not a fundamental issue (SharedArrayBuffer being disabled that is, not Spectre).

In a single-threaded setting, there isn't any significant overhead that I'm aware of between a browser and non-browser environment, aside from any overhead potentially incurred by executing on the WebAssembly VM rather than directly on the host machine. Of course, running single-threaded is less than ideal for other reasons.


When you say "they", I assume you mean "browsers"? Sorry for the dumb question. I know precious little about web programming and concurrency.


Except for webworkers (relatively new, I've not used them and know little about them), javascript in browsers is single-threaded. Even asynchronous things like ajax requests can only handle the result once the thread finishes doing whatever it was doing.


Interesting and thanks for the explanation. Does it have to be single threaded?


JavaScript in browsers was decreed by the standards committee to be single-threaded forever. Consequently, JavaScript is entirely lacking in concurrency constructs, e.g., implementing a region of mutual exclusion inside a JavaScript program.


Thanks! I assume that was for a reason? Or just oversight?


As far as I can tell, synchronous operation was not a necessary component of his model, but a pragmatic implementation mechanism in order to make at least some of the vision usable.

If you look at the various histories, it seem like Smalltalk 76 was the point at which the system was made practical, and Smalltalk 80 was a refinement of that. Alan himself has repeatedly said that he was not very happy with that version.


Message passing of Smalltalk-72 was quite complex [Kay 1975]. Code in the language was viewed by the interpreter as simply a stream of tokens. According to [Ingalls 1983]: "The first (token) encountered (in a program) was looked up in the dynamic context, to determine the receiver of the subsequent message. The name lookup began with the class dictionary of the current activation. Failing there, it moved to the sender of that activation and so on up the sender chain. When a binding was finally found for the token, its value became the receiver of a new message, and the interpreter activated the code for that object's class."

Thus the message passing model in Smalltalk-72 was closely tied to a particular machine model and programming language syntax that did not lend themselves to concurrency. SENDER was retained as part of the message-passing protocol, which is problematical for distributed systems. Also, although the system was bootstrapped on itself, the behavior of language constructs was defined (like Lisp) by an interpreter instead by their response to eval messages.


Well, and classes were "really" just sort of switch-statements matching that instruction stream, with the "methods" specific cases of that switch. All seems a bit distant from the conceptual model.


The Actor conceptional model had not yet been developed when SmallTalk-72 was designed following on SmallTalk-71. Later versions of SmallTalk after SmallTalk-72 adopted the Simula object model. Actors were developed for concurrency, which was entirely lacking from Simula. Also, Simula had procedures attached to the class hierarchy instead of Actor message handlers, which initially seemed like a subtle distinction that grew in importance.


I love Erlang; it would be fascinating (and an astronomical level of effort) to create an Erlang environment with the same powerful UX as Smalltalk.


Yes, Erlang dearly needs an IDE comparable to the one for Java Studio.


Programming in elixir with vscode and elixir-ls is a fantastic experience.


Visual Studio Code and and Elixir with Language Server Protocol are are good start but still have a long way to go reach the level of Android Studio with plugins.


could you elaborate? I've never worked with Android Studio...


I agree that message-passing should be asynchronous by default, but I disagree that message-passing "maps nicely" onto procedure calls in general. Only a particular subset of procedure calls can be naturally interpreted as message-passing: those with a distinguished receiver. Many mathematical functions lack such a distinguished argument, and Smalltalk and other OOP languages are unnecessarily awkward by forcing them to have one. IMO Erlang got it right by keeping a separate notion of function that doesn't involve message-passing.


Actor message passing "maps nicely" for use in procedure calls. For example Factor.[3] sends the message [3] to Factorial which sends back the return value 6. The procedural functions of Erlang are much more limited.


Sure, if functions are modeled as Actors. But I don't see the point of modeling the evaluation of a total mathematical function as asynchronous message passing (other than theoretical minimalism).


An Actor procedure need not be a mathematical function. For example, Random.[ ] could return a different result each time that it is invoked.


The earliest Smalltalk versions used actor-like communication, but then Alan Kay learned about Simula and was seduced by the promise of straightforward low-overhead implementation.


This is a quite inaccurate comment. The catalysts (in 1966) for the ideas I had included Sketchpad (especially), Simula I (a few days later), the ARPAnet (under discussion), operating systems inter-process communications (especially Project Genie), the Burroughs B5000, my old biology and mathematics majors. All these are mentioned in "The Early History of Smalltalk" that I wrote for the ACM History of Programming Languages"

Actors appeared after I gave a talk at MIT about the very first Smalltalk.

There were several later ideas that were discussed at Parc but not taken up because of the whirl that was already going on. One of these was derived from McCarthy's "fluents" and "situations" (essentially a labeled states/layers idea for allowing concurrencies without race conditions -- this was done very well in David Reed's 1978 MIT thesis.

Another was not waiting for replies. This was in the original set of ideas -- via biology and OS techniques -- but never got implemented in a deep way. The hardware we had at Parc was tiny and Smalltalk was expressive enough to fit a lot into a little.

Another set of ideas that were completely missed appeared in LINDA by Gelernter. This (and the larger ideas around it) are very good ways to deal with larger scalings, etc.


Actors were developed to synthesize and integrate a whole bunch of things including those mentioned by Alan above.

Additional important influences in addition to SmallTalk-72 [Kay November 1972] were Petri nets, Planner [Hewitt 1969], capability systems [Dennis and van Horn], semaphores [Dijkstra], the lambda calculus, and Scott-Strachey semantics failure to integrate everything using the lambda calculus.

1-way message passing is most important for hardware systems in which there is near certainty that a message will be delivered and there is no need to wait for an acknowledgement of receipt.


Thanks for correcting the record. I've read your HOPL paper but didn't remember.


In the HOPL paper is some discussion of the first design for Smalltalk, that I was working on when "the bet" happened and brought Smalltalk-72 to life as the answer to "the bet" (it used a combination of Lisp and Meta II techniques to win the bet). This made it quite easy to implement, and once Dan Ingalls implemented it, we started using it.

Smalltalk-71 was never implemented (and never had its design finshed, so there is less that can be claimed about it). But, germane to this discussion, I really liked Carl Hewitt's PLANNER language, and the entire approach to "pattern directed invocation" as he called it -- this was kind of a superset of the later Prolog, and likely influenced Prolog quite a bit.

The PLANNER ideas could be used as the communications part of an object oriented language, and I thought this would be powerful in general, and also could make a big difference in what children could implement in terms of "reasoning systems" (not just imperative action systems).

For Smalltalk-72 I used a much more programmatic approach (Meta II) to recognize messages (that also allowed new syntax/languages to be defined as object protocols (to win the bet) but this was not done comprehensively enough to really use what was great about PLANNER.

There were a few subsequent attempts to combine objects with reasoning, but none of them that I'm aware of were done with end-users in mind.

I thought the subsequent ACTOR work by Carl and his colleagues produced really important theoretical results and designs, most of which we couldn't pragmatically use in the personal computing and interface work on the rather small Xerox Parc personal computers.



Seduced sounds like it had a viable alternative. At the time, machines were mostly single-threaded, especially in the learning environment he was trying to conceive. In that case, there is no need for actor-like communication, except for mindset of it.


Actalk [1] provided actor-like asynchronous communication on top of Smalltalk.

[1] Briot, Jean-Pierre. “Actalk: A Testbed for Classifying and Designing Actor Languages in the Smalltalk-80 Environment.” ECOOP (1989).


Actalk was a useful early simulation. However, it did not provide a region of mutual exclusion for an Actor with facets.

See the following: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3418003


I’ve never used Erlang but have shipped production Akka+Scala systems and let me tell you, it’s hard. A big part of the problem is that Akka only supports cooperative multi-tasking: No blocking operations allowed (unless you pin the actor to a thread). This causes a big problem with the code: all implicit state must be made explicit. This creates a lot of boilerplate and makes some problems very difficult, especially with regards to TCP/UDP connections. Even with Erlang that doesn’t have these problems, debugging and writing actor systems are some of the hardest programming problems I’ve ever come across.

Also actor systems are fundamentally stateful and don’t mesh well with FP. Are they better than threads? Of course, but it’s a low bar. Hopefully some abstraction will be discovered that offer significant improvement over actors, but I wouldn’t hold my breath. Stuff like futures are great and can solve some problem, but are not general enough to replace actors or threads.


Could you imagine a layer that compiles down to actors? If that it too much of a stretch maybe something that can lint the protocol flows between actors. So you can have proof that fragmentation and re-assembly is done correctly. Or that message sizes are bounded. Or some other higher level property is proven true?


One thing I’ve thought of is that instead of messages, each actor exports a list of functions that it provides. Then other actors can call these functions and everything looks synchronous but under the hood each actor is calling other actors functions transparently. This would eliminate message bloat (too many kinds of messages) and would end up looking kind of like OO. So instead of sending the message: Sum(x, y) and eventually getting the response: SumReturn(x), I could just do something like: actorA.sum(x, y) that would look and act like a regular function call. Then actorA could implement sum like:

def sum(x: DbInt, y: DbInt) = DbActor.get(x) + DbActor.get(y)

With Akka, none of this is possible. Once actorA received the Sum message, first it would have to send the Get messages to DbObject and update some record indicating I’m waiting for two Get responses while also saving who I’m supposed to send the data to. Then I need to have a big match statement and update the record whenever I get that specific response. Once I finally have received the two responses, I look up where to send my message, and then finally send my SumReturn() message.

It’s a total nightmare and it sucks big-time. A lot of Akka code just ends up being maintaining these large dictionaries keeping track of all the unfinished stuff I eventually need to send various actors when I finally receive all the ingredients.

And it starts to get really bad when you have hierarchical actor structure and one of the terminal actors needs some information held by the top nodes. Then I either need to forward the message up one by one maintaining state along the way, or find the parent of the parent of the parent and send the message directly. And if I do that, now my root actors are talking to everyone else, and the information partitioning advantages of the hierarchy are broken.

There’s one actor topology that really shines though: directed acyclic graphs. If I only send information one way, like a waterfall, actors work really really well. Great for bioinformatics or other ML/data science pipelines.


> A lot of Akka code just ends up being maintaining these large dictionaries keeping track of all the unfinished stuff I eventually need to send various actors when I finally receive all the ingredients.

I don't think this is an Akka-specific thing. I work with NServiceBus, which implements long running processes using database-persisted sagas (sort of state machines), and we have exactly the same situation... NServiceBus even makes these big state-tracking dictionaries explicit (every saga has a corresponding "saga data" object).


>> As I've seen it said, Erlang is Smalltalk done right.

I was just listening to this today morning, where I heard Joe mention in the podcast something similar on those lines (though I believe he even mentioned about prolog+concurrency was wat led to Erlang and Plex)

https://softwareengineeringdaily.com/2015/11/02/erlang-with-...


I assert that the mental model held by the programmer, reducing complexity and making effective toolbox choices, is a significant benefit, too.


It's not precisely the same distinction, but this reminds me of a CS professor who talked about "blocking send" and "non-blocking send" message systems. He wasted no time in abbreviating them to "BS" and "non-BS".


> Erlang is Smalltalk done right

I disagree ...

In the Erlang model the messages are no longer just function calls with single dispatch. Imagine for example that method calls on String values would be asynchronous actor messages. That would be absolutely awful.

OOP isn't just about the actor model. OOP is also about encapsulation and polymorphism. In a static language, we're talking about row or subtyping polymorphism, i.e. methods of "ad hoc polymorphism".

And polymorphism and encapsulation transcend the actor model. I actually really hate the actor model, because asynchrony makes everything harder and actors are too low level, whereas OOP in general is passable as a paradigm in the right context.


> This maps nicely onto function calls, but considerably more power can be had by letting the actor optionally continue doing stuff while awaiting a response.

> Bacteria continue to metabolize after sending chemical signals to their brethren after all!

I absolutely love the metabolism metaphor but it doesn't map directly to computer programs as we write them today.

Metabolic pathways form an intricate system:

https://upload.wikimedia.org/wikipedia/commons/a/a8/Human_Me...

Glycolysis, for example: starting from glucose at the top, we can follow its decomposition in the cell all the way to the mitochondrion.

This is similar to Unix pipelines. However, on Unix we must manually construct the pipeline:

  find ~/notes | grep TODO
In a metabolism-like system, the mere existence of these programs would be enough to provoke computation. I'd write "~/notes" somewhere and the find and grep programs would somehow know the data was meant for them and automatically execute themselves.

Enzymes are analogous to programs running in parallel. They are completely independent and don't interface directly with each other. They implement a process just like functions do. However, they don't explicitly call upon each other to perform a task.

Substrates and products are analogous to data of highly specific types. Catalysis occurs when substrate and enzyme come close enough to interact. The enzyme takes the substrate from a shared environment and releases the product into the same environment where the process repeats with other enzymes. This environment is most commonly the cytoplasm.

Enzymes determine whether their substrates are compatible by their chemical structure. This points towards structural typing. When glucokinase phosphorylates glucose, it becomes a different substrate: glucose 6-phosphate. Likewise, when a function adds a property to an object, it's structure changes and so does its type.

For example, let's take the following functions:

  f : A -> B
  g : B -> C
  h : C -> D
In a metabolic system, there is no need to explicitly write code like:

  a = A(input())
  h(g(f(a)))
Rather, they would immediately consume values of type A when they enter the system and the output would be a value of type D in the environment. The computer would fully metabolize data whenever it becomes available and go idle afterwards. As if the programming language's virtual machine was constantly processing all objects in the system:

  for object in objects:
    for function in functions:
      if function.argument.type == object.type:
        result = function(object)
        objects.add(result)
        objects.remove(object)
        break
It would be like polymerase chain reaction: throw DNA polymerase, nucleotides, some ions, the target DNA and its matching primers in a reaction tube, heat it up in a thermal cycler and the copying will begin happen because all the pieces are in place.


What's interesting is that there's no "destination" for these chemical signals, they're just emitted into the void. There's no sense of sender/receiver, which has some correspondence to the blackboard model[1,2].

1. https://en.wikipedia.org/wiki/Blackboard_system 2. https://en.wikipedia.org/wiki/Blackboard_(design_pattern)


Yeah, exactly. The synchronous, one-to-one method calls of C++ or Java bare basically no resemblance at all to bacteria chemical signals. The former are essentially functions which dispatch on the first parameter.

The blackboard model isn't quite accurate though. Chemical signals have spatial locality, propagation delay, and half-lives. An asynchronous, peer-to-peer mesh network of actors would be a closer approximation to the bacterial signal model.


Something like a "place-based" tuple space would be more accurate, yeah.


See also: membrane computing/P systems

https://en.wikipedia.org/wiki/P_system


The original ideas behind Smalltalk did allow a void, as in modern day Kafka would be a void or a multi-cast IP address would be a void. There is no clear receiver, there can be multiple receivers or there can be none. Communication is ephemeral. Also, even with the single dispatch system of Smalltalk, we can easily model something similar to the Blackboard system.


Maybe not directly but I'm sure these organisms has knowledge of context which influence hormone release. I'd be surprised if it was totally or near totally blind.


> As I've seen it said, Erlang is Smalltalk done right.

As I see it, It's not, but it’d be a decent platform to build an asynchronous smalltalk on top of. One key thing it is missing is a convenient syntax for defining processes (either via instantiable templates equivalent to “class” in many OOPLs or as singletons equivalent to “object” in Scala) in terms of the message handlers and local state (volatile state of the kind that would be held in the process dictionary and the kind of “transactionally updated” state that, in Erlang, would be carried in function args.)


A process is simply a function that runs independent of other functions in the system + a mailbox + a handle + own memory.

So the convenient Syntax to define a process is a function declaration. Plus, calling "spawn" to start the process.

Of course, as other responders pointed out, there is also OTP, an established framework (not syntax) to define processes with predefined, battle-tested behaviours.


The Actor Model attempts to be as general as possible to support direct modeling and efficient implementation of all digital computation. For example, an Actor is not required to have an external mailbox, which is required for communication in Erlang [Armstrong, et. al. 1992]. Requiring an external mailbox is problematical for Actors because the mailbox would itself necessarily be another Actor thereby immediately leading to an infinite regress. Also, requiring the use of external mailboxes can slow requests between Actors because it would always be necessary to first deposit a request in an Actor’s external mailbox so that the request could later be retrieved. That a request is almost always sent and received without ever residing in persistent storage, where it can be left and later retrieved, is an important engineering goal for Actors.


Isn't that exactly what Erlang's gen_server does or am I missing something?


seems like OTP to me


While it's always dangerous to criticize a computer legend, I would add one more thing that I believe Alan Kay "got wrong about objects": objects are extremely bad at representing most types of problems in business software.

I mean this in regards to the "true" message-passing type of actor objects, and not OO as it came to be post-C++ and Java. The reason being is that actors 1) fundamentally involve themselves with state and time, and 2) connect subject and recipient together directly.

In relation to 1), most business problems involve _data_ processing, and the thing about data is it is immutable. It's essentially a solid state type of activity, and bears no resemblance to the organic, messy world of biology where things are changing. For problems like these, you really want to avoid state in your program, and persist state after data transformation in an actual database. Concepts like stateful actor supervision hierarchies, and what to do with clearing mailbox state when something goes wrong are fundamentally complex. Those concepts are also broadly unnecessary (and arguably harmful) for most request/response or async pub-sub type software. I can't count the amount of time I've wasted redoing systems on an actor model to be a simple "stateless" request model. On all of those occasions, the model that didn't persist state in-app was more reliable, easier to understand, less code, and more maintainable.

With respect to 2), actors basically violate all concepts of dependency injection. Actors need to know exactly where they're going (or you can add a proxy, and now your state graph is even more complicated), and even with message passing, program flow is dependent upon _time_ and the order in which a program is run. This is a large step backwards in my opinion.

I feel that languages like Elixir that pride themselves on stateless, immutable functions, have a deep contradiction internally embracing the actor model, because it is none of those things. Sadly, I also predict that this generation of Elixir programmers will learn the lessons of the Waldo paper the hard way: you can't just throw an actor model over the network and have it work great. (I think it's especially interesting that most of the success stories around modern day actor systems involve problems that can withstand data loss: telephones, IOT, content, media streaming, games, etc.)


> most business problems involve _data_ processing

To quote two other computer legends, Fred Brooks and Linus Torvalds are right: data not behavior is the more crucial part of programming.

Actors focus on the behavior, whereas the data is the part that is really important.

What `git log` does isn't all that important relative to how the git object model is structured, as the latter dictates the former.

> I can't count the amount of time I've wasted redoing systems on an actor model to be a simple "stateless" request model.

So much this. So often people forget that "it's just code". It can run on a toaster, or a mainframe, or a VPS, or a Docker container, or a Lambda function. The lion's share of code should be structured as input => output in the most straightforward way possible.


«What if "data" is a really bad idea?» — Alan Kay, https://news.ycombinator.com/item?id=11945722


Ah, it would seem that is in fact an underlying point of disagreement.


Even Git has a complicated behavior that changes. For example, each new commit changes the behavior of Git.


You're actually advocating for an "actor model" yourself, you know :)

The actor model of "actors as services" and "messages as API calls" that has practically won in the real world. It also has the advantage of not being tied to one particular VM and it's polyglot by nature. It also isn't something that can be "scaled down" to language level constructs, and maybe that's for the best. Cells aren't made up of organs or of other smaller cells (mythocondria are quite exceptions, and the pattern goes only one level deep).

I think the deeper truth here is that truly good architectures are emergent/evolved, not designed/architected... only problem is that they tend to emerge/evolve on time scales longer than the lifetime of most businesses, so you can't take the approach of "let's evolve the right architecture" and actually be able to ship anything on time. Don't know a "solution" for this other than staying humble and mentally flexible.

Quite zen-ish ironical to think that the deepest wisdom we got so far in distributed may be the "PHP + MySQL" model of statelessness, short live isolated processes and business-valuable state kept in specialized storage systems :) The language you use for message processing is largely irrelevant as long as you keep the processing of messages isolate and transient (eg. throw away everything, including leaked/overflowed memory, after processing a message and replying - most systems besides PHP still fail at this!), the data store is also irrelevant as long as it support some basic transactional guarantees and you can easily back it up and import/export data from it to other systems better suited for other tasks.


> the deepest wisdom we got so far in distributed may be the "PHP + MySQL" model of statelessness, short live isolated processes and business-valuable state kept in specialized storage systems

PHP + MySQL was necessitated for a class of computer programs that process durable, broadly distributed information.

And the "deepest wisdom" is to ignore the distributed nature as much as possible. Yes there is a primitive actor-like model but that is because we more or less have to meet business requirements. If it were possible to scrap the distributed nature of it, we would.


I think you're very confused. Systems like Elixir are powerful because with care they make it easy to design reliable systems where state is marked off in handleable places where state - the source of the trickiest errors - is contained and process faults are segmented and do not cascade.

You talk about supervision trees as if they are an avoidable source of complexity. Well if you're going to design a stateless request system you'll still need systems to manage dns lookups, elastic load balancing, and what is going to happen when a backhoe cuts the fibre to the data center where your code resides. That complexity will exist no matter what, it's just that when you use the beam you do it mostly in one language instead of having to wrangle reams of YAML with a tee ruler.

Have you ever deployed anything in Elixir? Your commentary makes it seem like you only have a superficial and incorrect understanding of the relationship Elixir has with statefulness and statelessness.

Hell, your primary complaint, is addressed right in the Elixir Documentation: "When not to use a GenServer" https://hexdocs.pm/elixir/GenServer.html#module-when-not-to-...

> If you don't need a process, then you don't need a process. Use processes only to model runtime properties, such as mutable state, concurrency and failures, never for code organization.

I strongly recommend you watch "boundaries" by Gary Bernhardt. This talk (which precedes elixir) exemplifies the programming philosophy that elixir has attained, though I don't know if that evolved organically or not. The point is, you have to accept that some parts of the universe are going to be stateful. You model those parts carefully, and then encapsulate it in a stateless core which precisely does only data transformations.


I've used Akka for a decade (basically as soon as it came out) and am quite familiar with systems designed by the Erlang/Elixir community.

While actors definitely are a powerful mechanism for reasoning about stateful systems, most business systems are better off without having to design their own _technical_ distributed systems or in-app persistent state model. There is a reason that dedicated systems like Zookeeper, Redis, squid, relational databases, etc. exist, and that's because those things are insanely hard. Those systems also do not need to know about your business problem.

The actor model, and Elixir w/ OTP subtly encourage coupling of technical, distributed systems problems in the same application scope as the business problem. This is where that thorny state comes into play, and I believe it's a fundamentally wrong instinct. (It is also an instinct that is relatively vocal within the Elixir community: "throw OTP at it!", "once you need to scale, you can just add more machines to your problem!", etc.).

I have a rule on design reviews. I ask the team "is this system a technical system or a business system?" If they say "both", I ask them to make it into two systems, one technical and one business. Most likely, a "simple" technical system that does what they want already exists, and we can use it. While OTP and actors are good at designing interfaces or protocols for distributed systems, writing bespoke error handling for your business code with actors is going to be buggy and wrong. Make the business code pawn off state coordination or storage problems to systems with known, provable properties and a remote interface. (That interface will also be usable by other languages, and not tied to the Erlang VM).


> The actor model, and Elixir w/ OTP subtly encourage coupling of technical, distributed systems problems in the same application scope as the business problem. This is where that thorny state comes into play, and I believe it's a fundamentally wrong instinct.

I disagree that Elixir encourages coupling those systems. While erlang comes with no such help, in Elixir, you should be decoupling your distribution problems from your business logic by putting them into different applications within the same umbrella. Sure, there will be a coupling in the sense that they will live in the same VM, but that is abstractly similar to "our microservices all live in {AWS/GCP/K8s}", especially given the level of process isolation that the BEAM affords you.

But for all intents and purposes (program design, testing, heck you can even pick and choose components for deployment) they are decoupleable.


> Sure, there will be a coupling in the sense that they will live in the same VM, but that is abstractly similar to "our microservices all live in {AWS/GCP/K8s}", especially given the level of process isolation that the BEAM affords you.

I disagree that these are conceptually similar. If all my microservices are in a certain cloud, it does not imply that any important state is maintained in those services and not a database. It also does not require me to have my services in Erlang or whatever the actor model is written in. Actor supervision systems (in whatever language) are essentially narcisstic: they have to be in control over the state management lifecycle.

Actors fundamentally are of the philosophy where some state lives in the application, and it's mutated by these incoming messages. This is totally different from systems that scale, where any application "state" is just transient from a request or message in order to facilitate a mutation in a dedicated store outside of the app. There's many reliability benefits to that: language independence, being able to know the operational characteristics of state management don't change out from under you when you release a new version of code, ability to query state operationally without having to run map-reduce across code in your cluster (how fun would it be to write "get me the relevant state of my system" for a custom-written actor hierarchy instead of SQL?).


> Actor supervision systems (in whatever language) are essentially narcisstic: they have to be in control over the state management lifecycle

I'm not sure why you think elixir discourages using databases. Phoenix by default ships with ecto bindings and you're encouraged to use postgres or Amazon rds or whatever for your bulk state management. Phoenix pubsub ships with redis bindings as an option if you'd rather not use distributed erlang.

Fwiw, I have written a multinode graphql query handler that pulls results from actors instead of a database (in this case using a database as a central source of truth is a bad idea because the data being queried lives in "its own databases", libvirt, if you are curious, distributed across nodes and having competing sources of truth can be very hard to manage). Handling these queries was not difficult. And your actors, btw, don't typically live in a very deep hierarchy anyways, you use something like Registry, which lets you query your actors in a flat fashion (the only hierarchical components are supervisors, which don't store much in the way of business logic state)


I think the belief is that if all your state is held in a transactional database, then your actors don't have any state in them and at that point it's not clear what they're doing with messages that the sender couldn't just do directly themselves by talking to the database.

My own experience with Akka style actor designs has been pretty poor. The problem is you lose the notion of a cross-machine call stack. Actors just have mailboxes and process inbound messages/outbound messages, which means you have to maintain state machines yourself and/or handle re-entrancy. Lots and lots of bugs in designs like that.

On the other hand, simply doing blocking RPCs across services where the RPC framework handles re-entrancy and stack consistency for you, it doesn't have that issue. That's how services like Google search are built. They shard across many machines vs talking to a database directly primarily for performance or to enforce team boundaries, not as part of an over-arching architectural design pattern.


Yeah probably 90% of actors in elixir hold state that is relevant to a connection status. Think, tcp state machine or http connection state. It is veeery nice to have a state machine back your inbound http long poll or websockets connections if you want to do serverside rendering or serverside content streaming etc.

The other case is when you're doing something cqrs-ey where you can have competing load balances requests and you need to make sure that writes are logged, committed, then executed, and reads are cached. You should use an actor for that but ultimately your data will reside in a database.


This is totally different from systems that scale, where any application "state" is just transient from a request or message in order to facilitate a mutation in a dedicated store outside of the app.

It's notable that this is the model on which the world is built. This is the model that runs almost all the biggest systems in the world.


Please tell me if I am wrong, but I get the feeling that you conflate the idea of "bad programming and design of a system" with the actor model in general.

You can do just as badly with an object oriented model as you can with the actor model. Bad applications will be bad.


Business software must deal with change! Even something as simple as a financial account changes behavior because the balance in the account changes.

Of course, many business Actors never change their behavior. For example, a quarterly report to the SEC never changes although additional reports to the SEC may correct information in previous reports.

Of course, you know that Factorial is an Actor that never changes behavior :-) Also, the (infinite) list of prime numbers is an Actor that never changes behavior.


Thanks for clearly articulating why the actor model is poorly suited to model data and control flow. In practice, the complexity of the control flow also amplifies the challenge of managing schema and control flow evolution. Particularly challenging when mixed with persisted messages, for example when using https://doc.akka.io/docs/akka/current/persistence.html


Kay was into discrete-event simulation. There's a nice example of that on an Alto, a little hospital queuing simulation, in "Personal Dynamic Media". That concept comes from Simula-67, a variant of Algol-60 used for simulation.

It turns out that discrete-event simulation isn't used much, and that async model isn't all that useful for other things. If you've never seen a discrete-event simulation, here are some in Flash, of Disney ride line management.[1]

[1] https://www.themagical.nl/simulations


You maybe right about discrete event simulation, having created one for my academic research years ago, I had the feeling it wasn't as useful (there are so many things you might tune to make it spit out what you want), but the same model is fun for making random simulations (games).


Actor-based programming really interests me. The concepts behind erlang and smalltalk seem to be the closest to an ideal model for the amount of distributed computing power we have today.

It seems like we should be able to formalize a generic meta-programming language that describes discrete components. 'actors' would be concepts close to functions that receive inputs and return outputs, everything inside would be generally considered synchronous. These actors speak to other actors via addresses. That idea was all outlined by alan kay. Furthermore, these actors can live anywhere. They can be running on a single CPU, live across multiple threads, cores, processors. Ideally you just throw more processors or remove processors from the mix and the system distributes.

Where the rubber meets the road is all the communication mediums. The lowest abstraction is your actor return address is a stack pointer. The highest is the return address is another system (what we currently do with RESTful communications).

Once all the inter-actor controls are sorted out, there would need to be a system of dynamic resource distribution. The profiler/scheduler should be able to identify things like - this actor is handling 85% of traffic, distribute the actor to 6 cores - the cost of the route from a core to a core on the same processor is X, the cost of the route to a core across a network is Y - distribute actors that have the most cross communication as closely as possible - payload size would also need to come into account

on top of that, tooling that identifies workload-specific needs could also be identified: - this set of actors should be considered critical and available on % processors - take into account things like inter-region availability.

A lot of these concepts have already been hashed out and you notice the parallels to things like network architecture. Ideally in the end a 'system' would simply be a pool of usable resources e.g. SYSTEM1: my laptop, an AWS server, my phone, my watch. Computation work is unevenly distributed to the system based on route costs, computational power...even storage capabilities and peripheral access (printers, cameras, etc). A new actor would get initialized in the network and it could be scheduled on your desktop or your tablet. Eventually your system would just become an amorphous workload.

My desktop might begin distributing subsets of things that currently run locally to my AWS server because it's closer to the public API i'm accessing. If actors were generic enough they might be identified and made a reusable pool for multiple applications.

Sorry for the stream of consciousness.


> Once all the inter-actor controls are sorted out, there would need to be a system of dynamic resource distribution. The profiler/scheduler should be able to identify things like - this actor is handling 85% of traffic, distribute the actor to 6 cores - the cost of the route from a core to a core on the same processor is X, the cost of the route to a core across a network is Y - distribute actors that have the most cross communication as closely as possible - payload size would also need to come into account

It's not that simple. Objects that can assume locality are written differently than those that can't. The optimize for fate sharing, low latency, etc. If you have just have a mesh of floating objects, you must remove all of those optimizations.


Above all allude to good ideas that need to be formalized, designed, implemented, and deployed.


The title of this post is very unfair to Alan, who has made important contributions. For example the SmallTalk browser was extremely innovative leading to modern Integrated Development Environments that have taken over software development. Also, SmallTalk was the first to use Simula class hierarchies for graphics, which are now used everywhere.

Objects as conceived in Simula-67 are not universal primitives of digital computation. However this was remedied in 1972 with the invention of Actors.

It is very important that Actors rigorously formalize the rules for digital computation. See the following:

    https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3418003

    https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3459566
In this way, computer systems themselves can participate in discourse with humans about the nature of digital computation and associated software systems.

Every important scientist (including Einstein) made numerous errors. Fermi received the 1938 Nobel prize [Fermi 1938] for the discovery of the nonexistent elements “Ausonium” and “Hesperium”, which were actually mixtures of barium, krypton and other elements. I have made quite a number of errors myself, although I am certainly not of the caliber of Einstein or Fermi! It is important to find and rectify errors ASAP. In order to make progress, we must make errors.

Reusable Scalable Intelligent Systems cannot be implemented using the current common programming practice of stateless sever code + relational data base. See the following:

    https://papers.ssrn.com/abstract=3428114


Hello Professor Hewitt,

Hmm...I don't quite understand your criticism, particularly as you yourself point out that "every important scientist (including Einstein) made numerous errors", and go on to describe some of those errors.

The fact that Alan is a towering figure who has contributed more to this field than I (and most people) can ever hope shouldn't mean that we cannot point out where we believe such an error was made, should it? That's not how science, even computer science, is supposed to work.

As a matter of fact, it seems even more important in the case of such an influential figure.

Of course the article points out that it was (a) a "brilliant" (quote from the article) construction and (b) the fact that it was wrong wasn't really Alan's fault, because he didn't have any large computer networks to look at, so (c) using the same brilliant construction with up-to-date inputs gets you a better answer.

The claims you make about the impossibility of things that are clearly being done seem a bit strange.


Doesn't scaling network interfaces down to be used in local processes present all the same problems as scaling procedure interfaces up? You still have to be aware of which objects are remote and which are local.


Runtime system knows approximate latency to intended recipient Actor of a message, e.g., this core, nearby core, nearby chip on the package, nearby chip in the same server, nearby server in the same rack, nearby server in the same datacenter, another datacenter, another IoT, etc. However, Actors can dynamically move and so the answer can change over time.


Latency differences aren't just a difference in degree. They're a difference in kind. Some interactions have to be sufficiently fast or they shouldn't happen at all. A runtime that minimizes global latency guarantees isn't sufficient. You also need to guarantee that certain individual Actor interactions are sufficiently fast, which is only possible through colocation. You could add a colocation constraint to your Actor scheduler, but now you're pretty much back to where you started.

And latency is only one of the problems. Partial failure is another. It's often convenient for authors of colocated actors to assume fate sharing. If they can't, they have to handle a much wider set of failure modes.


Do you think that the "difference in kind" should be frozen in application source code?

I agree that latency needs to be carefully controlled. But the software engineering can be complex!


Often, yes. Parameterization adds complexity, so for interactions I know must always be colocated, it makes sense to hard code the provisioning of a local actor.


Good luck finding all the places that various kinds of colocation have been hard-coded, e.g., same core, nearby core, same chip, same package, same rack, same datacenter, etc.


Not really, because it's rarely a problem when things are faster or more reliable than you expected.


Interesting post OP. From reading your paper about storage combinators it seems to suggest that this has been used in the Wunderlist app.

It sounds quite promising in theory but more people would need to try it before we know how beneficial it is.

Would it be possible to open source the library or be able to read an actual code sample ?

Might help popularize it better than if this knowledge were in a paper.


Thanks!

The paper actually talks a little about results: reports were of code size and productivity improvements in the 50% / 2x region.

I actually met one of the guys who had provided that written feedback a little later, and he told me that although he had written 2x, the actual results were more like 10x. The reason he wrote 2x was that he thought 10x would not be believable/believed. I have a feeling he is right about that ;-)

The libraries are open source, at https://github.com/mpw/MPWFoundation (The lively project also has a version https://lively-web.org)

The Stores/Storage Combinator base implementation is in the Stores subproject, so https://github.com/mpw/MPWFoundation/tree/master/Stores.subp...

There are quite a few more stores in Objective-Smalltalk (http://objective.st, https://github.com/mpw/Objective-Smalltalk), which provides linguistic support for stores/storage combinators with Polymorphic Identifiers.

The Objective-Smalltalk project also has a number of example compositions, but I do need more sample code.


FWIW, in re: distributed Smalltalk, check out Croquet.

> Implemented in Squeak Smalltalk, Croquet supports communication, collaboration, resource sharing, and synchronous computation among multiple users. Applications created with the Croquet software development kit (SDK) can be used to support highly scalable collaborative data visualization, virtual learning and problem solving environments, 3D wikis, online gaming environments (massively multiplayer online role-playing games (MMORPGs)), and privately maintained or interconnected multiuser virtual environments.

> Since release of the Croquet SDK in 2007, the SDK has not been under active development. All further development of the technology has occurred under the Open Cobalt effort.

https://en.wikipedia.org/wiki/Croquet_Project


> each Smalltalk object is a recursion on the entire possibilities of the computer.

I've heard Kay say this a few times, but it's always seemed wrong. Self similarity is a powerful scaling mechanism, yes, but Smalltalk objects and computers aren't self-simlar. Computers are Turing complete. They can become any machine. That's what they're good for. Objects can't.


Objects as originally conceived in Simula left out concurrency, i.e., region of mutual exclusion of an Actor.

See the following: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3418003


I'm unsure what this has to do with my comment.


Simula originated the terminology "object", which was then adopted in later versions of SmallTalk. Objects differ in fundamental ways from Actors.


What does this have to do with Kay's claim that objects are "mini-computers"?


What is your precise definition of "object"?


I don't have a precise definition, but regardless which of the many candidate definitions you choose, none are "mini-computers". A computer, if that term is to mean anything at all, is Turing Complete. It's a machine that can become any other type of machine. And unless your object (or actor) happens to be emulating a Turing Complete machine, it's not a "mini-computer".

The only self-similar architecture we have in computing is the virtual machine, not the "object", whatever that word means.


What exactly do you think that an Actor cannot do? Trivially, an Actor can implement a Turing Machine. However, there are some Actors that cannot be implemented using a Turing Machine, e.g., an Actor with unbounded nondeterminism.


Yes, an Actor (or object) can implement a Turing Machine. But most don't. Most are simpler machines. So this statement by Kay, for instance:

"each Smalltalk object is a recursion on the entire possibilities of the computer."

is incorrect.


Difficult to know since you don't specify exactly what an "object" is. Seems more reasonable that "Actors are a recursion on concept of interacting computers."


As I said, pick your definition of object (Java objects, Smalltalk objects, actor, etc.). Unless it's "Turing Complete machine", which isn't anyone's definition of object, you can't equate objects with computers. Saying objects are computers is like saying objects are bicycles. Some of them are, yes, but that's not the point.

What's your definition of computer?


Of course, a computer is an Actor ;-) This would be a joke except for the fact that Actors have been defined up to a unique isomorphism. See the following:

    https://papers.ssrn.com/abstract=3418003

    https://papers.ssrn.com/abstract=3459566


That's an interesting result, but you'll have to connect more dots for me. I don't see how the existence of a categorical theory of Actors relates to what we're discussing.


If a computer is required to be an Actor, then we have a precise characterization of what it can and cannot do.

Of course, an exact implementation of a computer as an Actor needs to be specified.


I think that was Alan's original conception of objects (at least as could could be implemented in Smalltalk-72).


The Smalltalk-72 model was extremely limited. Code in the language was viewed by the interpreter as simply a stream of tokens. According to [Ingalls 1983]:

    "The first (token) encountered (in a program) was looked 
     up in the dynamic context, to determine the receiver of
     the subsequent message. The name lookup began with the 
     class dictionary of the current activation. Failing 
     there, it moved to the sender of that activation and so 
     on up the sender chain. When a binding was finally found 
     for the token, its value became the receiver of a new 
     message, and the interpreter activated the code for that 
     object's class."
SENDER and stack of previous senders was retained as part of the message-passing protocol, which is problematical for concurrent systems.


No, most Smalltalk objects were not Turing Complete. Take numbers. Numbers are objects in Smalltalk. They're not Turing Complete.


He seems to hint a bit what he meant in this HN comment - https://news.ycombinator.com/item?id=11946935.

Also remember that what most people think of Smalltalk (ie Smalltalk-80) doesnt seem to be what AK thinks of Smalltalk (I get the feeling that the Smalltalk-72 approach is closer to the ideals he had in mind).


In-process REST sounds horrible inefficient.



Can you briefly describe what you mean by "In-Process REST" and what the benefits are? I tried to read your slides but couldn't understand them. Are these new in-process interfaces asynchronous? Or are they synchronous interfaces that adhere to REST principles?


I talk about this in the Storage Combinators paper[1]

In-Process REST essentially means applying the applicable mechanisms of REST in-process:

1) URIs

2) a few well-defined verbs

Benefits:

1) Common, concise and familiar API for talking about storage. (Check how much API surface there is for talking about storage variations in typical OO libraries. In Cocoa, for example, it's atrocious).

2) Paths. For some of the benefits of paths in general purpose programming, see The Programming Language Aspects of ThingLab [2]

3) Avoidance of the architectural mismatch between storage-oriented problem domains (most of them), and procedurally-based implementation technologies.

4) Composability: this is the heart of the Storage Combinators paper, you can plug these things together to obtain the desired functionality.

5) Performance, at least compared to other ways of getting similar benefits, such as (over-)used of filesystems, FUSE, Microservices for architectural reasons etc.

...

[1] https://www.hpi.uni-potsdam.de/hirschfeld/publications/media...

[2] https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.90...


I'll check out the paper, but at a glance, "a few well-defined verbs" seems to just mean you've introduced a set of standard storage interfaces, right? If so, calling this REST, to the extent which that word means anything at all, seems a bit of a stretch.


I just finished reading the paper and that’s what it looks like to me. It’s a CRUD interface w/ multiple implementations (proxy, cache, route, serialize, filesystem store, memory store), which they create pipelines out of using some syntactic sugar in the Objective-Smalltalk language. This syntactic sugar, discussed in their paper on ‘polymorphic identifiers’, allows you to use the same syntax for writing content to a file as you would writing to a key in a map or a field in an object. If you look at the pictures / tables in this paper, you get a general idea of what they are doing (e.g. replacing the filesystem store with a memory store while running tests). These dynamically dispatched processing pipelines are powerful, but can be hard to follow (which is true of dynamically dispatched processing pipelines in general). The power/obfuscation is multiplied by the convenient syntax of the language.

My opinion is that the actual innovation is the ‘polymorphic identifiers’ they introduced in their previous paper, which is definitely worth a read as my one line description of it can’t really do it justice. It’s kind of like being able to overload the assignment operator. Once you have that kind of feature, it’s sort of a no-brainer to say ‘Hey, all I/O (variables in memory, filesystems, etc) should use the same interface’.


For 10 years, people have been talking about REST as if it means something. I could never figure it out. If it means anything more than CRUD, it's lost on me.


I've had the same feeling and finally came to a few conclusion that Fielding's REST architectural constraints lead to client applications that are required to utilize undefined URIs for performance gains. The reason true RESTful APIs are scarce as compared to HTTP/JSON APIs is due to API developers recognizing this issue and choosing to deal with it by making URIs part of the API contract. The REST community has offered no additional guidance or answers other than to point out (correctly) that HTTP/JSON APIs aren't truly RESTful. That is OK.

Here is a much longer comment on reddit where I go through Fielding's thesis and a few other writings and show that following the constraints pretty naturally leads to the outcome above: https://www.reddit.com/r/programming/comments/2oyj65/api_des...


To me it means CRUD + stateless (no sessions) + naming convention. overall this is aimed at making api easier to reason about.


Well, and the URIs / Polymorphic Identifiers. The combination of these two is crucial. And yes, that's all it takes and yes, it is a bit odd. Took me a while to accept as well: the first system in the style was around 2004.

However, the In-Process REST paper[1] has a section listing the constraints of the REST architectural style, and we actually match those constraints very well, apart from the obvious bit of not being a distributed system.

The constraints (from Fielding's dissertation) are:

1. Client Server

That one's the obvious miss, as long as you define client and server as being separate computers.

2. Stateless

If you follow the model, then any state mutation is done via the "verbs", so yes. No more or less enforceable by the mechanism than in real REST.

3. Cacheable

Yes.

4. Layered System

See Storage Combinators. Yes.

5. Codee on demand (optional)

Doesn't make sense, so no. Optional, so no big deal.

6. Uniform Interface

This is the one with the URIs and verbs, so yes.

HATEOS was at least partially adhered to in the system we built, but again not enforceable by the model and also widely not adhered to in the wild.

[1] https://link.springer.com/chapter/10.1007%2F978-1-4614-9299-...


question: would you consider STL (standard-template-library) closer (in some sense of the word) "philosophically" to theory of objects than 'vanilla' OO ?

i mean, _i_ always find it intriguing that in OO, world can be decomposed in terms of interfaces that vary on a single type, whereas, imho, what you _really_ need is to parameterize your program by the structure of data that it manipulates i.e. a notion of bunch of interfaces spanning across multiple types...


Bit of a clickbait title... reasonable discussion.


They should not have class variables.


Cough cough. Seems someone has not really looked at the reality of the field.

Erlang is right there, i advise a look :)


The "reality of the field" is that the WWW was/is built with Erlang?

I definitely did miss that.

Back to the drawing board, I guess... ;-)


OOP feels right because it's an imitation of reality. In the reality we live in... a verb cannot exist without a noun. Think about it: how can there be "movement" without the "thing that moves." OOP makes sense from this perspective.

If however we think of abstractions outside of reality as we know it. Things like imaginary numbers, negative numbers and non-euclidean geometry appear.

Outside of reality an Object can be further divided into two things that don't really exist in the real world: pure state and pure action (from certain perspectives, ... technically the function still occupies state and can be thought of as an object). In CS it feels as if the discovery came backwards the real world analogue (OOP) came after the abstraction that doesn't exist in reality (mathematical functions and numbers).

Anyway, I'm taking this somewhere that has a relation to the parent article so hear me out. Function and State as separate entities is a smaller organizational subdivision then Objects which are Functions With State. Separating Function and State solves a big problem that is central to Designing Systems. The problem is associated with modularity problems grouping two primitives together into a single entity. What happens is more often then not we don't fully understand the true nature of the thing we are building. So in the initial designs of an OOP system we unionize a certain function with a certain state and it seems like the correct thing to do at the time, but over time as requirements change or we gain greater knowledge of the problem we're solving we realize that the function/method is better used in a separate context. But it's too late... at this stage the method that we want to pull out is dependent on the object and that object is a dependency for multitudes of other objects. Thus we either end up rewriting code or creating a hack, thereby inventing technical debt.

If that method was never unionized with state, if we used smaller subdivisions in our code where state and functions are separate entities then this would be much less of a problem. The function would not be tied to state and that function can be executed in the context of anything.

This metaobject post talks about the isomorphism between OOP and HTTP/REST/Servers and talks about the success of that model and how we should bring it down to the process level. I'm thinking what's the point? JAVA and smalltalk and a bunch of other languages fill that role you're just changing the semantics, but the structure is still the same.

He also fails to talk about the problems we have with concept of HTTP, REST and servers taken to the max. We have a term for this it's called "microservices." The problems we have with microservices are exactly the same problems that I just described. I described a problem with OOP and microservices are an OOP model. The difference here is rather then unionizing action and state with Code it unionizes state and action with real cold hard metal. The actual machine that holds state and "does stuff" is the object. So all the problems of realizing how to divide or unionize services and state in microservices are exact same problems that existed when trying to figure out what methods belong with what object in OOP, only when the actual machine is your abstraction barrier, things are much harder to change. The problem is well described even by OOP advocate martin fowler: https://martinfowler.com/bliki/MonolithFirst.html. His solution for this problem is less then ideal... basically... build it wrong first then tear it apart and mutate it into something else... I don't even know if martin fowler realizes that he's also talking about OOP as the design structure of microservices and OOP are isomorphic.

Anyway, I digressed... and I'm still disagreeing with the OPs conclusion. It makes no sense to me why we should bring this server/HTTP model down into code given the fact that it has all these problems and that it's basically already done with JAVA. The way forward is actually movement in the opposite direction. Don't abstract code into a server model rather abstract the server model into code. Basically code that doesn't compile into assembly language but compiles into apps that are automatically networked across a constellation of servers. It's a very hard thing to implement but being allowed to separate function and state at the server level like we do in code is, in my opinion, the way forward.

It also sort of exists in a minor way with the whole infrastructure as code thing and aws lambdas.


> The function would not be tied to state and that function can be executed in the context of anything.

Non-OOP functions are still written against the interface of their parameters, so they remain tied to state.


Interfaces are usually stateless as they only contain methods.

Technically, the function body itself is a form of state but I addressed this technicality above.


No, I mean the function (e.g. a C function) assumes a particular type of parameter. So it's bound to that type of parameter (by accessing its fields) in the same way a method is bound to its object. It's the same problem.


Instructions acting on types or interfaces in the purest sense are just actions and verbs... it is not state. The technicality arises when you consider the fact that for the instructions to even exist you must write the instructions down... the result of writing something down is the creation of state.

So to address what I think will encompass what you're referring to and a lot more than that... anything that is written down as code is in itself state, including types, interfaces and the function body. But as I mentioned before this is a technicality.

We pretend that the computer is executing functions when it is actually executing assembly code that imitates functions just like we how we can pretend functions are stateless even though their very existence implies state.


You can think of the computer representation of functions as a leaky abstraction as the statefulness of reality leaks into the virtual world.

There are languages that take advantage of this leakage by allowing you to pass functions around as parameters or aka first class functions. I'm sure you've seen this and used it as it's pretty common now.


I think the argument is that the difference between `noun.verb(state)` and `verb(noun, state)` is only semantic. The former is just syntactic sugar for the latter under the hood (or not. Look at python). The dependencies are the same.


I've seen your argument b4. I'm not sure if he's referring to that.

Either way it's more than semantics. Let's say I have two nouns. Noun1 and Noun2. Only Noun1 contains the verb method. Noun2 does not. How can I use noun1.verb on noun2?

I cannot use it. But I can use verb(noun, state) on either object. noun1.verb forms an arbitrary wall that becomes technical debt. Even modifying noun1.verb will not help, the only way is to move it AND modify it and change all the things that are dependent on it...

While verb(noun, ...) at most requires a modification, that's it. Semantically the "meanings" are the same but from a modularity perspective there is a difference in the "agility" of the method.

Let me put it simply. a method is tied to its parent object/interface by type AND scope of the parent instantiation. You cannot reuse the function outside of the scope.

A function is only tied to the type. Your argument is true if you're only thinking about types.

You might be thinking that the "type" of noun is really the same concept as scope and that if you get the type of the "noun" right then both examples are the same. You're not wrong in this sense but rarely do people place the right method on exactly the right interface or object. Usually they place it on an interface and realize (months or years later) that the method can be reused on 30 other unrelated objects. The change to correct the design on such a discovery involves a massive rewrite of a type hierarchy. It is a huge effort and often not undertaken.


I find it _much_ more likely that shoehorning `verb(noun1, ...)` to accept `noun2` will result in sub-optimal design than factoring out an interface (`verb(...)`) that both `noun1` and `noun2` will implement. A verb's meaning/implementation can shift dramatically depending on the noun to which it is attributed. E.g. `validate(user)` is materially different than `validate(order)`.

Any argument predicating on "massive rewrites" and "huge efforts" is a straw-man. A programmer can model dependencies incorrectly using either approach above. Expanding a discriminated union (to include `noun2`) will lead to the exact same amount of refactoring as extracting an interface. We know this because it _is_ a matter of semantics.

The discussion at hand is about comparing/contrasting the dependencies between object-methods (`noun.verb(...)`) and stand-alone functions (`verb(noun, ...)`). From the POV of the method/function, the dependencies are the same. Whether `noun` is explicitly passed as an argument or compiled into `this` does not change what information is available to the function body (i.e. it's dependencies).


>I find it _much_ more likely that shoehorning `verb(noun1, ...)` to accept `noun2` will result in sub-optimal design than factoring out an interface (`verb(...)`) that both `noun1` and `noun2` will implement.

I am not talking about the case you describe above. Get rid of Union types, that is a whole different thing. I am talking about a function that does not need to be shoe horned that can obviously be used for members of both noun1 and noun2 but due to a design decision the function is tied to noun1.

Let me give you a concrete example that is trivial. The example is so trivial that it probably won't happen in the real world but you can see how for concepts that are more complex it can happen all the time.

Imagine I have an app with a class in it called SortedListOfPeople. It would make sense to put a sort method on this class. Now imagine that this app runs for a bit then you add a feature which involves a class called GroupOfCats. The feature is very far away from and completely different from SortedListOfPeople. Then one day you get the task to turn GroupOfCats into SortedGroupOfCats.

Suddenly you got a problem. How do you re-use the code from sorted list of people without the sorted list of people? How do you use it with a different context at all? Not being able to reuse code is NOT a semantic problem. It is an intrinsic design problem.

Your genius solution above (Note how I call your solution genius instead of straw-man) is two implementations of the same concept. This is obviously more genius than one function.

Sortable types is an obvious example, but think about how often this problem occurs in OOP for more complicated concepts. You end up implementing things multiple times all over the place.

If the sort function was a stateless function operating on a sortable interface parameter you would not have this problem. The only thing you need to implement for GroupOfCats is to give it a sortable interface rather than reimplement sort for everything that needs to be sorted.

Actually if you think really hard about OOP and the sort function. How would you make a generic sort function that can be reused across all contexts while following the rules of OOP? You cannot use static classes and the function cannot take parameters... as that would imply a stateless function. The sort function must operate on the classes own state. How can you then share the sort method across two or more classes in OOP?

The answer is inheritance. Put the sort method in the base class. How would you share the sort function with classes that already have ancestors? Multiple inheritance. Too bad everybody hates these two concepts so they resort to garbage syntax that basically turns the class into a namespace so they can start using functions as if they weren't tied to some intrinsic state.


You have simply contrived a poor design, then pointed out its flaws. Not only that, you are _constraining_ the solution space (no static methods and must be parameter-less). That sounds like a straw-man to me. There is nothing inherent in OOP that mandates a developer choose your design.

Furthermore, you are comparing a perfectly factored functional design to a poorly factored OOP design. I can just as easily assert that `sort(people)` cannot be factored into `sort(comparable)` because "clearly" the body of `sort(people)` is dependent on some property of `people` that is not present on `comparable`. Contravariance cannot just be assumed.

Said another way, every function is tied to the entire public surface of the data to which it is provided (at least in theory). So unless `people` just happened to have exactly the same interface as `comparable` already, a new, more generic, `sort(comparable)` function would be necessary.

The solutions are roughly equivalent in either paradigm: move the behavior into a more generic container. Where composition can be used instead of inheritance in tandem with double dispatch, for OOP, if the public surface of SortedGroupOfCats must include a `sort()` function.


>You have simply contrived a poor design

I contrived a poor design that is a reflection of what can happen. There is no design theory making all designs perfect therefore many isomorphic mistakes in my design happen all the time uncontrived.

>Not only that, you are _constraining_ the solution space (no static methods and must be parameter-less).

I constrain it to prevent you from creating a function instead of a method. A function is outside of OOP a method is 100% OOP. a static class without state is equivalent to a namespace holding a bunch of functions. If I didn't constrain you you'd be proving my point by switching to using a regular function instead of OOP.

>There is nothing inherent in OOP that mandates a developer choose your design.

That is the problem. There is something inherent in functional programming that mandates the developer to never be able to make the previous design choice. If all your functions are stateless there is NO way you can even contrive the poor design I contrived earlier. That is the secret. Less expressivity is less ways to screw up. Imagine a programming language where the syntax only allows you to design your programs in the best possible way... Functional programming is closer to this goal than OOP.

>I can just as easily assert that `sort(people)` cannot be factored into `sort(comparable)`

It's better to factor something to be more general than to write 10 flavors of the same thing. How hard it is doesn't matter here, rewriting sort to solve this problem is better than OOP where you have to write another version. That is all I am arguing for.

>Contravariance cannot just be assumed.

The entire essence of my argument is that you discover this property in two random places, but you cannot apply a function in both places because the function is tied to another context.

>a new, more generic, `sort(comparable)`

And this is better than two specific flavors of sort. Let's scale this up to an app that's been around for 5 years.... in this case One generic sort is better than 20 different flavors of sort.

>The solutions are roughly equivalent in either paradigm: move the behavior into a more generic container.

But like I've been saying you can't just move the method in OOP you have to change the object hierarchy. If the method is to be used in another unrelated object hierarchy than the hierarchies have to be merged via multiple inheritance. All I want is the function, why am I bringing the whole object ancestral structure with it?

"I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."

    -joe armstrong


>I can just as easily assert that `sort(people)` cannot be factored into `sort(comparable)`

You are misunderstanding what I am asserting here. If the body of `sort(people)` is coupled to a property of `people` not present on `comparable`, you cannot _just_ refactor to a more generic `sort(comparable)` and expect the behavior to remain consistent. The solution requires two functions: `sort(comparable)` that abstracts the portion of the function body concerned with the surface of `comparable`, AND `sort(people)` that interacts with the property of `people` not present on `comparable`. It's obviously worth noting here that `sort` could just as easily be some other verb, say `validate` (I understand `sort` doesn't make much sense in this particular context).

Similarly, there is no need to "move the method" or "change the object hierarchy". You conveniently failed to quote the one line in my response above that addresses this...

>composition can be used instead of inheritance in tandem with double dispatch, for OOP, if the public surface of SortedGroupOfCats must include a `sort()` function

And while it makes for a great quote, Joe Armstrong is simply wrong. There is nothing inherent to OOP (and even less so when examining the languages that utilize the paradigm) that requires poor design. What if I wanted "the banana that one specific gorilla is holding in some particular jungle"? How would that look in OOP vs functional? More importantly, is there a meaningful difference or is it just semantics?

FWIW, I prefer a functional paradigm myself.


>You are misunderstanding what I am asserting here. If the body of `sort(people)` is coupled to a property of `people` not present on `comparable`, you cannot _just_ refactor to a more generic `sort(comparable)` and expect the behavior to remain consistent.

Refactor sort into the composition of two functions. One is reliant on people, the other is just sort. The signature of the original sort function remains the same. But you have a general function that can be reused in different contexts. This type of change CANNOT be done on OOP.

>composition can be used instead of inheritance in tandem with double dispatch, for OOP, if the public surface of SortedGroupOfCats must include a `sort()` function

composition moves the entire state across. Think about it. I have SortedGroupOfCats and you want me in order to reuse sort, move SortedGroupOfPeople into SortedGroupOfCats.

>And while it makes for a great quote, Joe Armstrong is simply wrong. There is nothing inherent to OOP (and even less so when examining the languages that utilize the paradigm) that requires poor design.

OOP is bad because it makes it possible to shoot yourself in the foot, and while avoidable, unfortunately the syntax promotes designs that tend to be bad.

>More importantly, is there a meaningful difference or is it just semantics?

There is.. a function does not need to be instantiated to be used. A function never modified an entity. That changes everything. The fact that lambda calculus and turing machine exist in a theoretical sense shows you that the industry knows about an intrinsic difference between the two concepts.

>FWIW, I prefer a functional paradigm myself.

Note that although I have used the functional paradigm as an example this is not my overall critique. I am not just saying OOP is better than functional programming. I am saying OOP is worse than imperative, procedural and functional programming. Out of all of the most popular ways of programming... OOP is the least modular and the one with many many design issues. Even non-functional programmers have noticed this... GO is one of the results of this realization.

There are cases where OOP works, but in most cases, it's sub-optimal.


>The signature of the original sort function remains the same. But you have a general function that can be reused in different contexts. This type of change CANNOT be done on OOP.

This is where you lose me. The above is simply untrue. There is absolutely no reason one cannot employ `SortingAlogrithm.sort` within the body of `people.sort()`. Are you suggesting every utility class (and by extension the entire standard lib) needs to be injected into an object before it can be used?

Again, you are creating a false-equivalence. A "fair" example would require our generic `sort(comparable)` be passed into the original as a dependency like so: `sort(people, csort)`. It's _possible_ that the two functions could be composed, but that is completely dependent on the semantics of each function (we cannot just assume composition is possible for every use-case).

The difference between a first-class function and an object is purely semantics. Yes, you have to `new` an object before you can call a method, but there is no requirement any "implicit context" has to be provided at the time of instantiation. Closures offer the exact same capability via "capturing" and partial application.

Conceptually, all OOP does is take 10 functions that all have the same parameter and "partially apply" each of them over that parameter into new functions that don't have a parameter (i.e. they get that "implicit context"). That is, OOP saves you a step. It is common practice in FP to start with "pure" functions then, higher up in the application, partially apply those functions into new, domain-specific functions with your data (a.k.a. dependency injection) that are strung together to carryout a use case. OOP allows you to _define_ partially applied functions instead of the need to compose them (of course this also makes the DI mechanism a bit different as well).

I can agree that the above _can_ make decomposition more difficult, but your general critique reads more of one leveled at programmers not understanding the basic principals of software design, not OOP. I could just as easily create a mess in FP as in OOP.

>More importantly, is there a meaningful difference or is it just semantics?

I urge you to sit down and write out the pseudo-code. It will be plainly obvious that there is no meaningful difference between each approach.


>This is where you lose me. The above is simply untrue.

No. My statements are true. But you are right about you being completely lost.

>There is absolutely no reason one cannot employ `SortingAlogrithm.sort` within the body of `people.sort()`

Let's talk about objectives and what you're implying with this suggestion. The objective is to sort a bunch of people within the the people class.

What is SortingAlogrithm.sort? Is it an object that sorts its own state or is it a stateless class with a stateless function? If it's an object that sorts its own state then it CAN'T do what I want. I want it to sort peoples' state NOT it's own state. If it's a stateless class with a stateless method then you are no longer doing OOP you have created a function that is not tied to state thereby proving my point that stateless functions are more modular.

>Are you suggesting every utility class (and by extension the entire standard lib) needs to be injected into an object before it can be used?

Yes this is the model in pure OOP languages. Otherwise you are using a hybrid of OOP and functions thereby providing evidence that OOP alone is broken. Things like JAVA, C++ do all kinds of BS like this further ingratiating my point.

>Again, you are creating a false-equivalence.

No. You are failing to understand. 'Lost' was the term you, yourself used.

>A "fair" example would require our generic `sort(comparable)` be passed into the original as a dependency like so: `sort(people, csort)`.

This is not fair at all. in fact it's ugly. I pass a generic sort as a parameter into a specific sort? If you think that's fair and you code this way then you're in for a surprise when building a complex system.

>It's _possible_ that the two functions could be composed, but that is completely dependent on the semantics of each function (we cannot just assume composition is possible for every use-case).

I'm thinking you got confused by what I mean by composition. There are two compositions I am referring to in my statements. Function Composition and Object Composition. Object Composition is exactly the previous example you gave sort(people, csort) or inserting something via the constructor People(csort), there's a number of ways to do it.

Function composition of A(x) and B(x) is F(x) = A(B(x)). Function composition is at the heart of functional programming.

>It's _possible_ that the two functions could be composed, but that is completely dependent on the semantics of each function (we cannot just assume composition is possible for every use-case).

If you're not changing state and something has properties of being sortable it can be done.

you have x = sortableListOfPeople

composedFunctions = f(sortableListOfPeople) {return doPeopleSpecificStuff(sortGeneric(sortableListOfPeople))}

you can go the other direction if you want. The implementation is situation specific but if something is sortable this can be done always but only if all functions are stateless and don't modify values. Note composedFunctions should have the exact function signature of the original function thereby negating the need to change other things dependent on it.

>The difference between a first-class function and an object is purely semantics.

No. A first-class function cannot change. It is immuteable and when called with the same input Always has the same output. An object with a method when called at different times can have different output based off of internal state. Your statement is categorically wrong.

>Yes, you have to `new` an object before you can call a method, but there is no requirement any "implicit context" has to be provided at the time of instantiation.

Then you are basically declaring a stateless function. The admission that you have to do this is an intrinsic flaw. Yes nothing prevents you from going outside of the OOP paradigm when programming in an OOP language. You likely have no choice due to inherit flaws in OOP. Try doing OOP without this ability... You'll see how broken the whole model is.

>Conceptually, all OOP does is take 10 functions that all have the same parameter and "partially apply" each of them over that parameter into new functions that don't have a parameter (i.e. they get that "implicit context"). That is, OOP saves you a step. It is common practice in FP to start with "pure" functions then, higher up in the application, partially apply those functions into new, domain-specific functions with your data (a.k.a. dependency injection) that are strung together to carryout a use case. OOP allows you to _define_ partially applied functions instead of the need to compose them (of course this also makes the DI mechanism a bit different as well).

Let me tell you definitively. I completely understand what you're talking about, I understand it now and I understood it before. No need to reiterate. What you're not understanding is that despite my 100% comprehension of your statements I am telling you, that you are failing to understand ME.

Let me try to put it as simply as possible. If there is an Object called Number and a method called addOneAndReturn... if I call that method on a Zero Twice? what happens? I get One and then I get a two. The method is returning two different values despite being called in the same way TWICE. This sort of thing is NOT POSSIBLE in a stateless function. It Cannot Happen period. THIS is the fundamental difference. THIS is MORE than semantics.

Yes I understand that currying and first class functions have parallels to Dependency Injection BUT there is still MORE then a semantic difference between OOP and functions.

>your general critique reads more of one leveled at programmers not understanding the basic principals of software design

Design is the domain of things we don't understand. There is no design in type theory or number theory because we understand all the implications of the axioms and theorems. We go to design when there is no formal theory that can say why one design is better than others.

I am not coming from the design perspective. I am coming from the theory perspective. When you tie functions with state the state must move along with the function. If you keep state and functions separate then they can move separately. This is logic. No design principles needed.

Any language with too many design patterns is a language that is very much lacking in a formal description (think: JAVA)

>I could just as easily create a mess in FP as in OOP.

You can. But there are more ways to do it in OOP then in FP. In fact there are more ways to shoot yourself in the foot in OOP than in procedural programming in general. I'm not even referring soley to FP. Even C is better than C++.

>I urge you to sit down and write out the pseudo-code. It will be plainly obvious that there is no meaningful difference between each approach.

You don't need to write psuedo code. Use your brain dude. A stateless function versus a function that carries it's own mute-able state. There is MORE than a semantic difference. The semantic difference you're referring to is when the SELF value is immuteable. In that case the two ways of calling the function become more similar, but THAT is NOT OOP.

There is one rule that needs to be followed to make ANY program FP. Just one rule, that's it. Make all things immutable. OOP is fundamentally different from this because it involves the mutation of Object state.


I fear the goalposts have moved from "the problem with OOP" to "the problem with mutability". These are distinct concepts. The fundamental premise of OOP is, among other things, the encapsulation of behavior _with_ data (as opposed to _around_ data). It has nothing to do with mutability. One can absolutely practice OOP with immutability (looking at Scala here). Similarly, OOP doesn't need to be written imperatively. Are you suggesting `sorted_people = people.sort()` isn't OOP?

You are building your own concept of OOP to argue against, not the one that exists in reality. (straw-man emoji)


>I fear the goalposts have moved from "the problem with OOP" to "the problem with mutability". These are distinct concepts.

No they ARE not. For OOP to work you have to have objects be mute-able. The modern definition of OOP are nodes that communicate with each other and change each others state.

The minute you get rid of mute-ability is the minute you are doing functional programming.

>One can absolutely practice OOP with immutability (looking at Scala here).

Scala is a multi-paradigm language. Also scala is not always immutable. The objects in scala can mutate.

>Similarly, OOP doesn't need to be written imperatively.

It does if you want the properties of an object to change. That is the dichotomy of OOP and FP otherwise there is no dichotomy and no point for there to exist vocabulary to separate the two concepts.

>Are you suggesting `sorted_people = people.sort()` isn't OOP?

If I remove the engine from my car and roll it down a hill while in the drivers seat am I still driving the car? Yes. Technically I am. But key feature of the engine moving the car is missing. You can call people.sort() OOP you can also call it not OOP because it is missing the key feature of self modification.

>You are building your own concept of OOP to argue against, not the one that exists in reality.

lol lets look at the definition. https://en.wikipedia.org/wiki/Object-oriented_programming

I directly quote from the site above:

"Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data, in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). A feature of objects is an object's procedures that can access and often modify the data fields of the object with which they are associated (objects have a notion of "this" or "self"). In OOP, computer programs are designed by making them out of objects that interact with one another."

Key sentence above: "A feature of objects is an object's procedures that can access and often modify the data fields of the object with which they are associated"

The goal post was never moved. It was set by the Actual definition of OOP. I am explicitly including this feature to make sure that ALL we are dealing with is OOP and not some hybrid functional or OOP merger.

>(straw-man emoji)

This mechanism of turning macros into emojis doesn't work here on HN! Similar to your arguments.


The quoted text above is referring to objects, not OOP -- which encompasses a number of concepts. You can refer to the the "Features" section in your link above for the complete list (notice one is missing). For further information about the relationship between OOP and mutability just google it.

It's a waste of time to argue about a topic that is orthogonal to your original claim (before the goalposts moved): that the "problem" with OOP is how you invoke a function -- `noun.verb` as opposed to `verb(noun)`; As if those two systems are subject to different constraints. They are not. It is just semantics.

The above isn't to say there doesn't exist consequential differences between OOP and FP, but I absolutely did _not_ engage in this discussion to argue about OOP vs FP in general. I think we actually both agree FP is a superior paradigm.


>The quoted text above is referring to objects, not OOP -- which encompasses a number of concepts. You can refer to the the "Features" section in your link above for the complete list (notice one is missing). For further information about the relationship between OOP and mutability just google it.

Makes sense. Object oriented programming isn't programming revolving around objects. You totally invalidated all my claims with this genius argument here. You also invalidated the entire definition of OOP and wikipedia too.

>It's a waste of time to argue about a topic that is orthogonal to your original claim (before the goalposts moved)

At first I was going to say that the goal posts have not moved but your brain was the thing that moved. Then I realized nothing has moved. Your brain is still in the same place: A place of ignorance.

>that the "problem" with OOP is how you invoke a function -- `noun.verb` as opposed to `verb(noun)`; As if those two systems are subject to different constraints. They are not. It is just semantics.

Let me spell it out for you where the goal posts were when the conversation started. The goal posts were in a place where 99.999% of all programmers operate: In a universe of mute-able variables, mute-able objects and immutable functions. Typically "goal posts" start here because that's how 99.99999% of all programmers operate.

So within the bounds of these "goal posts" you made a claim that OOP and Functions only have a semantic difference. Which is CATEGORICALLY wrong. So then I decided to let you know that your CLAIM is only TRUE if VARIABLES are immutable you acted AS IF we were ONLY talking about immutable variables ALL ALONG as if that's the primary assumption all programmers make when talking about programming. The goal post wasn't moved... get it? I only changed the context a little to show you WHEN your arguments would be true, BUT nowhere did I "change the goal posts."

What happened after that is that YOU changed your argument. You imply that you've been ONLY talking about immutable variables all along and that I changed the goal posts to suit my needs. I can tell you 100% that you are either a liar or deeply delusional. You literally refuted a sourced definition of OOP in your attempt to stay relevant. Give it up dude.

>The above isn't to say there doesn't exist consequential differences between OOP and FP, but I absolutely did _not_ engage in this discussion to argue about OOP vs FP in general. I think we actually both agree FP is a superior paradigm.

I agree that we aren't talking about FP vs. OOP. What we are talking about is, and I quote:

"that the "problem" with OOP is how you invoke a function -- `noun.verb` as opposed to `verb(noun)`; As if those two systems are subject to different constraints. They are not. It is just semantics."

Which you are completely and utterly wrong about. You cannot make this statement without saying that all variables are immutable which is not a property that can be ASSUMED OUT OF THIN AIR.


Tone it down.

OOP is not "programming revolving around objects". Simply using objects in your code-base does not mean you are practicing OOP anymore than using functions means you are practicing FP. But I digress. Again, the definition of OOP is unrelated to the claim which prompted this discussion.

>The function would not be tied to state and that function can be executed in the context of anything

This is the (your) line of text at the top this thread (go look), and the point to which I wanted to offer some clarification. It has nothing to do with the definition of OOP or mutability vs immutability.

The point I have been trying to make is that `noun.verb` and `verb(noun)` are both tied to the same state; That the _caller_ of either of these functions needs to have a reference to `noun`. In this way, the "context" is also the same (both functions can only be executed in a context that has `noun`). There are problems with OOP! The above just isn't one of them.

>You cannot make this statement without saying that all variables are immutable

How do you know `noub.verb` mutates `noun`? Similarly, how do you know `verb(noun)` doesn't mutate `noun`? There is no way to make an assessment of the mutability of `noun` given those snippets. Which is kind of the point: they are equivalent.

Making an assertion about the behavior of either function (when that behavior is _clearly_ not shown) is building an argument against something else entirely. This is called a straw-man. Similarly, shifting the argument to be about the definition of OOP and/or mutability instead of the relationship between how a function is written and its dependencies/modularity is to argue about something else entirely. This is called moving the goalposts.


>How do you know `noub.verb` mutates `noun`? Similarly, how do you know `verb(noun)` doesn't mutate `noun`? There is no way to make an assessment of the mutability of `noun` given those snippets. Which is kind of the point: they are equivalent.

This is why you have to make every possible assertion. Muteable and immuteable. And in the muteable case your statement is wrong. Since we are talking about OOP (see first sentence of my first post) by definition only the muteable case is considered. There was no movement of goal posts, you simply failed to see where the goal posts were originally.

>Making an assertion about the behavior of either function (when that behavior is _clearly_ not shown) is building an argument against something else entirely. This is called a straw-man. Similarly, shifting the argument to be about the definition of OOP and/or mutability instead of the relationship between how a function is written and its dependencies/modularity is to argue about something else entirely. This is called moving the goalposts.

The topic at hand is on OOP. That's because that's the topic in my first post. Thus since OOP is mentioned in my first post THE DEFINITION of OOP is what I'm talking about. And since mute-ability is part of the DEFINITION of OOP this is the topic. What happened is you entered this conversation with the idea that the goal posts were somewhere else. They were not.

We are talking about noun.verb and verb(noun) in the context of OOP. That is the topic. And the statement of verb(noun) is the same as noun.verb is categorically wrong. You are wrong. This is concrete.


typo up above:

>I am not just saying OOP is better than functional programming

I am not just saying functional is better than OOP


> This metaobject post talks about the isomorphism between OOP and HTTP/REST/Servers

Emphatically: no, it does not. It makes the point that these are not isomorphic, at all.


I disagree. He says that the rest model could be better then message passing when scaled down, then he says Alan Kay is right about messaging but that it's just one aspect of his storage combinator concept.

What I interpreted as what the author means by "what Alan Kay got wrong" is that Alan was just looking at the wrong isomorphism. The wrong aspect of essentially the same concept.

Outside of the OPs opinion though. I personally believe that there is an isomorphism between infrastructure and OOP.


>> ...no, it does not. ...

> I disagree. He says ...

Hmmm..."he" (check my bio) might disagree with your assessment of what "he" said . ;-)

> the REST model could be better then message passing when scaled down

Yes. That's because it is not isomorphic to objects+messages (but built on top).

> then "he" says Alan Kay is right about messaging but that it's just one aspect of his storage combinator concept

Neither of those. It think he's right about generalising from "messages" to "interstitial/ma", which to me (and possibly only to me) means "connectors". That's the general concept (connectors define architectural styles, REST and objects/messages are specific architectural styles). Now of course "messaging" is closer than most other attempts.

> Alan was just looking at the wrong isomorphism.

Not really, he was scaling down the wrong thing, because the scaled up thing did not actually exist, so he had to imagine it, and when it did come into existence it did not match what he imagined it to be (and scaled down).

             scaled up                    |   scaled down
     ------------------------------------------------------------------
      1.   imagined computer network      |  objects and messages
      2.   real global network (WWW/REST) |  In-process REST
And my evidence for (1) being wrong is that scaling objects and messages back up did not actually work.

         scaled down            | scaled back up
     ------------------------------------------------------------------------
      1. objects and messages   | CORBA/SOAP (Note on Distributed Computing)
      2. In-process REST        | WWW/REST

Anyway, thanks for reading and thinking it through!


Ok, did not realize you were the author, apologies.

I think we're talking about different things so let me be clear about what I mean.

By isomorphism I mean a loose equivalence where the only thing that needs to be equal is structure. AN isomorphism between two different things is the preservation of Structure. In this specific case the structure I'm referring to is:

  a system consisting of many (nodes that hold state and specific methods for communicating) that are talking with one another. 
This is what I consider to be the modern definition of OOP and is isomorphic to Alan Kays definition of message passing between objects(nodes) that hold state.

Anything that is isomorphic to the structure above is isomorphic to each other.

So using "=" to refer to an isomorphism what I was referring to is that:

JAVA = smalltalk = Servers communicating via REST = SERVERS communicating via SOAP/CORBA = objects and messages = OOP = nodes holding (state and methods) and communicating with one another.

So because all these things have an isomorphism they all have the same "Structural" problems as OOP that I described above and they are all basically different aspects of the same thing.

I think what you mean by "REST" worked and "CORBA" didn't is that CORBA/Soap didn't become popular while REST/WWW did, but if you look deeper there is no difference between the two concepts. CORBA/Soap is isomorphic to OOP which is isomorphic to REST. All these things are built off of a model where "nodes with state and methods communicate with one another" Maybe one system became popular because the semantics were easier to grasp or maybe there was a lot of extra fluff that needed to be implemented to get things to work. Either way I am not referring to this surface level difference. I am talking about a deeper OOP problem.

I am referring to an innate structural problem that comes with nodes that have both methods and state. The unionization of these two things leads to a design flaw.

Imagine one node with state that has a method that needs to be used in the context of another state. For example I have a node called SortedListOfCarsByPrice that contains a sort method. Later on, (possibly years later where design requirements change), in the design I realize I need an additional object called SortListOfPeopleByAge object. The design problem I hit here is that I want to reuse the sort method but it is tied state of SortedListOfCarsByPrice . The method modifies the state of the SortedListOfCarsByPrice object so I cannot pull it out and reuse it in another object that needs to modify the internal state of SortedListOfPeopleByAge. This is a trivial example of something that happens all the time with OOP and anything that is isomorphic to OOP.

Of course I could of had Parent Object with that method and had SortedListOfCarsByPrice and SortedListOfPeopleByAge be children of it, but my argument is saying that because the process of design makes it so that we may not know about this connection in the beginning. We usually come to realize this flaw later on in the game where it is essentially too late to change. OOP hits all kinds of design problems like this AS does microservices because the concepts are Isomorphic.

So to solve this problem Functions and state need to be subdivided into a different structure: The following definition is not isomorphic to OOP:

   A system where there are two entities: Functions and Data. Functions only take input data and output new data. Data is an immutable unchanging piece of information that contains no methods. 
By splitting Objects into its constituent pieces suddenly no sort method is ever tied to the data of SortedListOfCarsByPrice. By it's very nature the sort method is tied to nothing and can be moved across the context of all sorts of data. I can use sort to sort lists of cars by price and People by age because sort is not tied to data.

If you understood what I said above maybe that will help my original thesis above be more clear as it certainly looks like I have failed to communicate my main point.


Mutable state is an implementation detail. It is tricky, it induces confusion, maybe it should be avoided whenever possible. But if you are using a physical computer, that is all that you have.

And, in those very same computer implementations, data cannot possibly contain methods. The compiler is going to treat them as ordinary function implementations, not any different from functions generated by the compilers of non-OO languages.

So I would love to see a mathematical proof of the isomorphisms you mentioned but, especially, a proof that your proposal is actually non-isomorphic to the other examples. I don't believe that holds true.


I never said anything was non isomorphic. It all depends on how high you go to look at everything. Technically if it’s Turing complete it’s isomorphic. So all styles of programming are isomorphic because they run on a Turing machine.

However when you don’t take a birds eye view Of everything a dichotomy exists between say OOP and say functional programming. We don’t talk about these two styles of programming as if they are the same thing just because an isomorphism exist at a different level. We view these two concepts from a different context and in that specific context OOP is different then FP hence the different naming. At the Turing machine level both styles coalesce into assembly language and at this level they are the same.

So at all levels above the Turing machine. If OOP is defined as nodes with states and methods communicating with one another.... Then microservices, rest and corba form an isomorphism. Because that’s what microservices are... nodes of servers with state and methods...

Surely this is different from a stateless pipeline of functions where data is put into the front of the pipe And a new value pops out of the other end of the pipe.

No mathematical proof can describe this dichotomy because essentially Turing completeness says everything is the same thing so I’m operating at a different level if you know what I mean. Maybe a smarter person has a way to illustrate this dichotomy formally but all I’m going for is you understanding what I’m talking about.


> Surely this is different from a stateless pipeline of functions where data is put into the front of the pipe And a new value pops out of the other end of the pipe.

By no means I want to sound inquisitive, but, if it is different, how is it? Is one of these models (OOP and functional) a subset of the other? If so, do they represent levels in a given hierarchy? If not, do they even intersect? Are dissimilar properties complementary?


a turing machine is isomorphic to a lambda machine. You can build a turing machine in a lambda machine and a lambda machine in a turing machine. A lambda machine is basically a functional program.

https://www.wikiwand.com/en/Turing_completeness

Procedural programming and functional programming occupy the same level in this sense. I know of no "theory" that describes the evident dichotomy between the two. Lambda calculus is different from a turing machine simply because it feels different. Just like there is no theory illustrating the difference between JAVA and elixir. The only theory available says that everything is the same if it's turing complete.

As i mentioned before however, there are issues with things that can actually exist in reality. I do not believe an actual lambda machine can be built in reality but in the theoretical world you can, if you wanted to, define something as "lambda complete."

As for OOP and functional programming, again there is no formal description of the dichotomy.

In fact there is no formal description of OOP. OOP is not part of computability theory and to my knowledge not really studied by theorists. OOP is just a popular style that exists in the business world.

So in short the available academic knowledge implies that all of these isomorphisms occupy the same level. That is there is no hierarchy or no one has tried to map a hierarchy yet.

You might be right that there is a hierarchy. I would think lambda calculus sits at the apex simply because lambda calculus separates the concept of state and function while OOP and turing machines combine the two primitives (Object = (state * function)). However from other perspectives, there is no real world analog of a lambda calculus machine so from that perspective you could organize the hierarchy by putting OOP or the turing machine on top because it can exist in reality. I don't subscribe to the later... I would say the smaller division of primitives is the more fundamental concept and therefore higher on the hierarchy.


FWIW, Fowler's "Monolith First" engineering idea is closely related to Christenson's "Modularity Theory" economic idea.


Title assumes that Alan did in fact get anything wrong, which is incorrect


History needs to reevaluate Alan Kay as most of what he said was wrong.

Here is a cringe worth talk from 1997 where he claims the internet is disfunctional because it nit standalone and that all developers should build their own tools and compilers.

https://m.youtube.com/watch?v=oKg1hTOQXoY


"HTML on the internet has gone back to the dark ages because it presupposes there should be a browser that understands its format."

This is the fundamental reason the modern internet is centralizing to a couple of entities.

"This has to be one of the worst ideas since MS-DOS."

Yes, the modern browsers have taken us back at least 20 years when it comes to UI design, component based programming and distributed systems. The web is an endless stack of kludges, held together by duck-tape. I can back this up, in case you don't believe me.

But, we should ask ourselves how it became such a stack of kludges. And I think Alan hits the nail on the head.

"At the very least, every object should have a URL. I believe every object on the internet should have an IP. It represent much better what the actual abstracts are of the physical hardware to the bits. "

Beautiful insight. The URL does not scale from a systems perspective. In my designs, I see myself often using UUIDs and negotiation to transfer knowledge about entities. The URL (and RESTful communication) is an annoyance. It fixates the location to something arbitrary. I can only use one hierarchy to denote the same object. I cannot easily renegotiate how the same things are named in my systems. And, the worst: the type of the object is often included in the url. `https://foo.bar/person/123` fixes the type 'person' to the object 123.

URLs nowadays are an abomination. Most are not durable, contain excess information and suffer from projecting the information against an arbitrary axis.

Finally, all developers should at least once have implemented their own compiler. It is not a hard experience at all and can be done in around 100 hours by someone with a medium skill in programming using a good text-book. It gives a truly fundamental insight into systems design and should simply not be skipped.

W.r.t. tools: I assume any programmer worth his salt has build at least some of their tools! Perhaps I should start asking this at interviews. If you don't understand how your tools are built, how can you even begin thinking about building tools for others? And how can you understand, without attempting a crude build yourself? All the hard things are already done, the literature is there, the books are written.

So, anyways, you make a bold claim: "most of what he said was wrong." This is awfully vague, I am afraid. Lets make it a challenge. Except for him making predictions, I challenge you to come up with a couple of statements from Alan Kay, and we'll discuss it.


> This is the fundamental reason the modern internet is centralizing to a couple of entities

The reason Google and Facebook dominate the internet has little to do with technology and much to do with economics, namely scale economies and network effects.

> Yes, the modern browsers have taken us back at least 20 years when it comes to UI design.

I'll grant that CSS is a mess. What else?

> I can only use one hierarchy to denote the same object.

You can create multiple URLs that point to the same object.


> The reason Google and Facebook dominate the internet has little to do with technology and much to do with economics, namely scale economies and network effects.

And the network effects can only happen because there is a tight coupling between the data (contained and scrambled within the HTML page) and the presentation. Had data and presentation be decoupled, I could have combined network information from various providers and present it to me in one overview.

> I'll grant that CSS is a mess. What else?

Everything. The security model, the fact that the user has limited control over their identity (and it is not build into the protocols), the multi-threaded nightmare, no component standardization, very limited integration with low-level OS systems, the 'one-tab is one context' metaphor, the box-model, the misfit of page-to-interaction (and the history API), the non-existent voice interaction, the color-model, the DPI/pixels/ems transformations, the invisible local storage we barely manage, the resource utilisation, the lack of uniformity in the programming model, the peer-to-peer communication (WebRTC does not count, since it is not standardized and has quite some flaws), the lack of (easy) customization abilities (high-contrast websites), the limited math-rendering, the enourmous complexity... I could go on.

> You can create multiple URLs that point to the same object.

Yes, but they would not denote equivalence. I could use a redirect, but then one system needs to know the representation of the other. It would be fundamentally asymmetric.

Also, it would contradict the term Unique Resource Locator.


> no component standardization

This one is interesting because Alan Kay is so expressly against it ("browsers shouldn't have features"). He thinks every object should just render itself as a bitmap.

Besides, browsers do have standard components. That's what the semantic HTML tags like <button /> are. The specific views vary between browsers. Is that a problem? Regardless, almost no site uses the standard stylings that do exist. They use a de facto standard like Bootstrap, or they build their own.

I agree with some of your other points, but I don't understand your lament of "enormous complexity" while you're advocating for more features.


One thing about rendering to a bitmap is that it is more robust over time. Look how much code is in the browser to support older styles of html. Smart endpoints dumb pipes, prevents ossification. The browser tries to be far too smart for its own good and ends up making a mediocre job of everything while being hugely complex (as outlined by parent above)


I guess he meant: browsers shouldn’t need features. It should be a network effect. It’s good to make a distinction between design principles and their resulting effects. Most of what I described before were effects of bad design.


> And the network effects can only happen because there is a tight coupling between the data (contained and scrambled within the HTML page) and the presentation. Had data and presentation be decoupled, I could have combined network information from various providers and present it to me in one overview.

I'm with you in principle. I think such a modular architecture based on standard interfaces between objects is exactly what we need, but I don't see how it precludes e.g. the dominance of Google. Google dominates because they have all the search data. People don't want non-Google results in their views. Non-Google results are worse.


> Also, it would contradict the term Unique Resource Locator.

wikipedia defines URL as "Uniform Resource Locator", and i don't think i've ever heard "unique" in there


Yes, it was rather late yesterday. It is actually the dichotomy that a URL should be a unique resource identifier for a specific resource, but unfortunately it isn't. When we place a link, we want to be able to query on that specific URL (or URN, as it has become). We want to do late binding, since it is cheaper and more powerful.


what benefits do you see in URLs being unique? not necessarily disagreeing, just curious


- distribute resources at multiple locations

- reverse index to resources

- Easier to move resources

- Cryptographic signatures on id’s


> Had data and presentation be decoupled

"What if "data" is a really bad idea?" -- Alan Kay (https://news.ycombinator.com/item?id=11945722)

I just don't see how he has ever pushed for the direction you're advocating. If anything, it's the opposite.


As usual with Alan Kay, it's difficult to understand exactly what he's advocating for. On the one hand, he laments browsers having features, and thinks they should just execute code safely and provide a low level graphics interface, but on the other hand, he advocates for a universal polymorphic graphical object mesh (perhaps approximated by LivelyWeb) which would require a great deal more "features" (think Java) and is closer to what OP is describing.

I think he's made his way into this thread, so I invite him to clarify!


I read it as the browser should just be a VM. It's low level. Then the objects that run in the VM provide the features. This solves a lot of problems. The browser is slowly evolving to do this anyway with js, then wasm (reality is proving Kay right) but the underlying high levelness of it makes it a mess. A VM is more flexible and robust and at the same time simpler than a browser


The difference is that, in order to foster composition, the platform can't just be a VM. It must also provide the interfaces by which objects interact. Take Java. It's not just a VM. It's also the JDK/JCL.


The URL is just an opaque path appended to a domain. You can just as easily use an UUID instead of an hierarchic model. No component in the system gives a damn.


Yes, but it still presupposed on a specific location. Alan Kay (together with many other experts on system design) is a proponent of late binding. To do effective late binding, we should be able to route requests to the correct location in a distributed fashion. With a (regular) URL we make a weird amalgam of DNS (with IP) and path of the object. As we've learned from the transition from XML-RPC/SOAP, unnecessary complexity removes value of the system. I believe Alan argues that URLs introduce unnecessary complexity which can be tackled by lower OSI layers. However, to make that work, we need globally unique identifiers and routing, using IPv6. We want names (and/or identifiers) to be the fundamental element of our systems, instead of resource locators.

In other words, you want to be able to walk into any library and use ISBN as the underlying identifiers, instead of the Dewey system, which is not broadly applicable. You might get the Dewey number from the ISBN and vice versa, but the ISBN resource assigned to a book resource is the fundamental linking pin.

So, when we link from an article, we want to be able to say: "it is this specific article I am linking to, you figure out how to get access to it." This will be much more durable than an ephemeral URL.


> where he claims the internet is disfunctional because it nit standalone

Can you quote this or link to a timestamp? Searching these words in the transcript yields nothing.


> History needs to reevaluate Alan Kay

You're being downvoted (as I expect I will be, for agreeing with you), but having lived through the time when people blindly worshiped Alan Kay, I could not agree more with this statement.


Blind worship is always wrong. However, most admiration for Alan Kay's work is by knowledgeable people and for good reasons. Including the committee that decides on Turing awards...

I am quite certain that my admiration for the man and his work is not blind.

And do note that I described his construction as "brilliant" in my post, despite the fact that he ultimately got that part wrong.


Ah. Alan Kay himself said that he feels they got the boundaries/sizing wrong when designing OO, so there really shouldn't be any mention of 'blind worship'. As far as I am aware, Kay has never been caught up in his own hubris.


source?




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

Search: