Alan Kay: Smalltalk is not about objects, it’s about messaging (1998)
lists.squeakfoundation.org
lists.squeakfoundation.org
mybool ifTrue: [ ^ 2 / 4 roundup ]
The outer part of this expression reads “send a closure expression, as the message ifTrue, to the receiver mybool.”Keep in mind, this `mybool` could be anything! It just so happens that there are two runtime objects, `true` and `false`, that satisfy the “Boolean interface” (consisting of messages ifTrue and ifFalse) the way you’d expect—with `true` running any closure passed in an ifTrue message, and `false` running any closure passed in an ifFalse message. (For those of you who know combinatory logic, these objects are essentially the K and SK combinators.)
But consider that you can create your own object that implements this interface—with whatever arbitrary logic you like—and substitute it for a Boolean as the value of `mybool`, and no code will be able to tell the difference.
This is what is meant by “computation as messaging.” Branching is a request to an opaque “branch-evaluating object.” At each step of computation, you’re not getting the runtime to compute—you’re getting your objects to compute. The objects are the computers, and the messages are the instructions to those computers!
I'm actually not sure whether Smalltalk passes arguments by value or reference. But they're definitely not constantly updating like spreadsheets or FRP behaviors.
Are the variables "copied"? I would say so because if you call the containing method multiple times the same code will create new block-closures and they will refer to the variable environment that existed when the block was created, so they will each have their own separate set of outer scope variables.
Unlike JavaScript inner functions, Smalltalk closures can cause a return from the containing method (but need not).
Here's a good article: http://esug.org/data/Old/vw-tutorials/Adv_VWNotPrintable/Blo...
(Although ruby doesn't implement if-condition in this 'pure' way).
Thinking in terms of C-style variables may be unhelpful. Instead, consider Smalltalk “variables” as scoped names bound to heap-allocated objects. You can bind as many names as you like to a given object at any time, and each name is usable within the bounds and duration of its scope.
What the VM does under the hood is more complex than that, natch, but as long as the user-level abstractions don’t leak (which they shouldn’t) the mental model works.
EDIT: This is not sarcasm. I am an idiot and he is right.
That is, the way in which the message is specified changes the classes of errors and development challenges one might encounter. If function arguments are positional you get positional errors; if they are named you enter the business of defining many more types. Messages may return a different version of themselves(e.g. arithmetic) or a different category of message. The later in time the message is delivered, the more it has to carry around its own context. And when a message has unbounded scope it has unbounded complexity.
And like with a lot of things, there aren't silver bullets. Many things can be defined as a single enumerated value. Sometimes tuples are nice, sometimes records are nice. Sometimes the problem expands to the point where you have to have a message with a little computer inside it - but when that happens it's usually not for the purpose of any specific task, it's to add a layer of programmability into the system.
I've toss in cap'n'proto & surely others who let you write messages that talk about still outstanding messages. Still short of referring to full on streams in your messaging.
https://capnproto.org/rpc.html
There's hx-uri that allows for referring to other http messages, which I think could fulfill this in http.
https://github.com/martinthomson/hx-uri
Here's to expanding the medium! Let the hypertext ever hyper.
When I first heard of it, maybe I was initially skeptical because of the name, having been traumatized years ago by Cap'n Software Forth on my Apple ][, which John Draper developed to implement SleazyWriter while incarcerated in the Alameda County Jail.
https://archive.org/details/EasyWriter_Capn_Software_John_Dr...
>EasyWriter was a word processor first written for the Apple II series computer in 1979, the first word processor for that platform. It was written by John Draper's Cap'n Software, which also produced a version of Forth, which EasyWriter was developed in.
>Draper developed EasyWriter while serving nights in the Alameda County Jail under a work furlough program.
Dr William Cook (who designed much of Mac OS’s “RPC-plus-queries” IPC model) explored a more generalized approach where whole self-contained chunks of behavior can be sent across the wire for remote evaluation. As I recall, his Batches model uses a “safe” subset of familiar JavaScript operations, but any behaviors could be supported as long as they are guaranteed to terminate.
http://www.cs.utexas.edu/~wcook/projects/batches/index.htm
There’s nothing inherently magical about this: it’s just a carefully-restricted form of remote execution where the client-supplied “code” to be executed is incapable of doing nasty things like access restricted APIs or lock /blow up the server with infinite loops or stack overflows. Beyond that, it’s just a question of whose CPU you want to burn more, and how much time you want to spend bouncing between them.
Thinking about it from a JavaScript or C mindset will make your head ’splode, but for Lispers (of which Smalltalkers are a spinoff) it’ll be a big fat “how obvious”. Be nice to see it further pursued.
edit : i misread your post, thought you were designing a system, not a PL... maybe you can still answer my question ?
In my experience with Elixir there are never many processes that have a state to save in a database. Eventually it's the same load no matter if the implementation is actor based or object oriented.
The general idea is to build the transaction in a pipeline of function calls. Each function gets the old definition of the transaction as an argument and returns a new one. Eventually the call at the end of the pipeline executes the statements in the definition, wrapping them in a transaction.
If the question is how to deal with 100 or 100 k concurrent transactions, the answer again is in the same way a cluster of servers running programs written in any other language do. Eventually SQL is SQL.
I think i’m still not clear on how you would design a transactional system ( such as order & paiement processing ) with actors in a way that won’t make it look like a microservice based system ( aka : one per subtask, fetching info from a db for each incoming request, and storing the result in a db in the end)
It seemed to me actors had to have a more fine grained context ( such as one per order), but in that case i’m wondering how it’s supposed to handle saving its state regularely so that no information on the order processing state is ever lost
I've never heard of an actor system able to automatically respawn actors with their previous state (the whole system would look like a tree of cached data, each layer responsible for saving the leafs under it, with a huge "persist to DB" on top , wouldn't it ?). Does erlang OTP do those kind of things ?
Edit: also, are you Prof "Carl" Hewitt ? The one that invented actors ? I'd be honored you found a question of mine about actors excellent...
The key insight is that an actor is a tiny VM and you just need to make the VM state durable.
- not all states transitions need to be durable. If performance matters you’ll have to get your actors to call something like a « saveState » from time to time, otherwise every single property change is going to have an overhead in terms of performance.
- what does « durable » mean ? Surely, just having the state of an actor saved in the memory of the supervisor isn’t enough. If the two are on the same server and the server shuts down, it’s game over. You need a « persistence » service / actor, but that thing is going to have to persist the state of all the actors in your system. I don’t see how that can work if you’ve got millions of actors running in parallel.
https://en.wikipedia.org/wiki/Oracle_machine
https://arstechnica.com/tech-policy/2019/11/supreme-court-wi...
(In the cover photo of that article, it looks like people have been urinating on the Oracle logo!)
So I would qualify 'best' to meant "easiest system that 95% looks like actors that you can quickly get up and running and doing useful things and or play around with"
Satisfactory implementation for Reusable Scalable Intelligent Systems with millions of Actor cores for quadrillions of Actor are under development, but not yet publicly available.
I deployed an actor-based system model to completely rearchitect and replace an inefficient stateless python system. There have been zero runtime or concurrency errors since I deployed (unfortunately sometimes the hardware the actors are modeling fail us) but patches to work around the hardware failures are simple.
•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 (PID) becomes inaccessible. Also, since Erlang is not strongly typed, cyberattacks can be launched against dangling references to processes that no longer exist.
•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.
For example, if process X on node A sends a message to process Y on node B and the network goes down, that doesn't mean process Y won't send a message to process X when the network resume.
You have made the point exactly. The problem of dangling processes is made worse by the fact that Erlang is not strongly typed.
Especially objects that need to be pinned by ref counting.
My personal interpretation of the Actor model is a message passing system to model the real world.
In the real world I can shout out "make me a sandwich" and even if I perceive that there is a process that could potentially satisfy that request, I also work on the assumption that for any number of reasons... the channel of communication going away, misenterpretation, the agent or myself dying, the resources not being available or delays due to manufacturing may impede progress.
That is just the way the real world works.
Although Future/Promise can be built easily on top of Actor, they are optimistic at best - especially in diatributed cases; which, if my interpretation is correct is your reason for Actor in the first place.
Actor is powerful so in my opinion, future/promise and building ref-counts on top undermines the core of it.
Pat Helland, after numerous years of distributed transaction work completely did a 180 in the face of reality.
There is nothing wrong with Actor.
As far as strong typing, I personally believe that is a completely orthogonal conversation. I just cannot see the conflation.
Strongly typed to me means I send an argument to a function and it happens to match the signature.
Well, if I have a strongly typed function that takes two integers and somehow is supposed to return the sum. I guess it is helpful to know that as the user-agent (caller) of the function, but that knowledge only helps programmers, not computers.
Strong typing is definitely not a model of reality when dealing with humans as functions, and often humans are end-point "processes" (heh) of function calls.
Not that I am against strong typing in any way. I just believe the Actor model is cleaner without mixing things up.
Certainly agree Erlang is great but not the ultimate.
Well... my 2c and thanks to you, Alan, Joe, Hoare, Sussman, Gelernter et al. et al. for the inspirations over the years, and appreciate you plugging away at the models to this day.
Have a wonderful holiday.
> As Erlang became popular we were often asked "Is Erlang OO" - well, of course the true answer was "No of course not" - but we didn't to say this out loud - so we invented a serious of ingenious ways of answering the question that were designed to give the impression that Erlang was (sort of) OO (If you waved your hands a lot) but not really (If you listened to what we actually said, and read the small print carefully).
Oof.
In Hewitt's actor model, messages are also (immutable) actors. At least in the early version I read about.
previous important contributions including lambda calculus
[Church 1932] (first-class procedures), Petri nets [Petri
1962] (true concurrency), semaphores [Dijkstra 1962-1963],
packet switching [Baran 1964] (concurrent communication
between computers using fixed size packets), capabilities
[Dennis and van Horn 1966] (pointer security), Simula [Dahl
and Nygaard 1967] (class hierarchy), and SmallTalk-72
[Goldberg and Kay 1976] (bit-mapped computer graphics and
browser, the precursor of modern programming Integrated
Development Environments). The lambda calculus could not
express concurrency with the consequence that lambda
calculus programs can be thousands of times slower than
Actors. A Petri net cannot dynamically expand and has the
limitation that tokens can miraculously disappear from
different places, which is inefficient to implement.
Capabilities have the limitation that pointers must be
manipulated using indexes into capability lists thereby
making programming awkward. Simula did not implement
concurrency and did not have messages. SmallTalk-72 used a
byte stream of program tokens thereby making it awkward for
concurrency.
See the following for more information: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3418003
The monad (https://wiki.haskell.org/Monad) is another mostly unsuccessful lambda calculus way to try to address some of the issues resolved in the Actor Model.
I only knew about actors for its scheme influence and the fact that it was highly concurrent (even this I'm not so sure). I got into college way too late for it to be studied sadly.
Pharo is a modern open source Smalltalk that was derived from Squeak. Nice UI, vibrant ecosystem and community. https://pharo.org/
It looks like I want to start with Pharo Launcher, so I've installed the Pharo VM and Pharo Launcher, but when I try to start Pharo Launcher a window pops up and it just exists. All I can see in the terminal is a message about pthread_setschedparam failing, which does not seem fatal.
I wonder whether my problem is with not understanding what I'm doing, or with NixOS, or something in between...
I'd guess you could get some hints by running ldd on the vm (I'm assuming it's not strictly a static binary).
You need the vm and an image - the launcher I imagine is a vm and just enough image to display a menu and get a "proper" image downloaded.
Perhaps I'm missing something but I doubt the Linux VM download on that page will help me. Ordinary binaries don't tend to run on NixOS without at least some ELF patching due to the Nix store architecture.
Just spotted this on Cuis smalltalk github page:
"If you get this error message (you won't get it if you run Cuis as root or sudo):
pthread_setschedparam failed: Operation not permitted ...
Then you need to do this (just one time):
sudo cp Cuis-Smalltalk-Dev/squeak.conf /etc/security/limits.d/squeak.conf
Log out and log back in, or reboot the machine."
Might be something similar for pharo?
https://github.com/Cuis-Smalltalk/Cuis-Smalltalk-Dev/blob/ma...
That is possible in other many other languages, including system ones like C.
Which doesn't really mean much when it comes to the standard developer experience and how that differs between programming a live image and the tooling built around that versus manipulating text and the tooling built around that. One lives at the abstraction level of running code and the other at the level of syntax.
In C/C++, I can do live debugging, reverse-time debugging, edit-and-continue debugging, multi-device debugging, multi-threading debugging... We also got language servers, on the fly retesting and recompiling, all kinds of profiling... All that with free or open tools readily available, not to mention paid, proprietary ones.
In higher-level languages, fancy features are even more prevalent.
So, what are we missing exactly from Smalltalk?
Learning the Smalltalk “world”/vm is an essential part of learning Smalltalk the language. Many people are initially put off by this but if you try to find a “Smalltalk without a Smalltalk-specific IDE” you are missing out on an enormous part of what Smalltalk is.
0. https://www.cs.virginia.edu/~evans/cs655/readings/smalltalk....
Digging into the code, I found that the VM just gives a code block access to all top level objects by default, but that isn’t a requirement. You can instantiate your own “global” and run the block within that. Back then I believe it was being used because old closures needed to see old top level class objects that might have been changed in the meanwhile. So you can use this to sandbox functions.
I would imagine it isn’t perfect, but it could be made so.
The os has become a passive tool to run stuff on, but once it was an involved middle layer that the user might be tapping throughout their use, with the os & user capable of understanding exposing & controlling some of the items in the program space. Now the program is more of a blind space to the os, doing it's own thing. Adjusting a program's volume & resizing it's window is all the interaction most users project from os space into program space.
More general systems research please!
I guess developers always implement their own ignorance to a certain degree (and how could they not!? except, you know, spending more time learning their craft). What they don't know the OS can do, they will recreate - often poorly - in their own software. It does get rather comical and the various frameworks that you can subscribe to in modern programming languages repeat the concept, just that now, you're reimplementing things that the language can already do, but in the framework.
My favorite example is the various kinds of "service" patterns, like dependency injection - in many instances that I've seen, you could boil most of its use down to simple function calls. But since global functions are ew, you instead create a service that you inject and then call a method in. Boom, the gods of OOP are happy and nobody is allowed to say: "Wait a minute, if I take a few steps back and squint a little, what you're doing looks surprisingly similar to a global function call."
(And yes, I know, DI is supposed to be about overriding functions, yada yada very few people actually do that and it's often such an organizational hassle with downstream problems that projects eventually resort to put up conventions from doing that most of the time. Anyways - tell that to the people that I've seen implement, I kid you not, a CamelCaseService.)
So if you have a class that uploads files to a cloud storage provider - it would make use of IStorageProvider, and you might have AmazonS3StorageProvider, AzureBlobStorageProvider, LocalDiskStorageProvider, and for testing you would have DevNullStorageProvider.
A program using DI without interfaces with different implementations is missing the other advantages of DI.
Soon there will be packages with millions of Actor cores for quadrillions of Actors. Operating systems, e.g. Linux, will be inadequate for Reusable Sacalable Intelligent Systems.
See the following: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3428114
That's kind of the point.
https://www.gnu.org/software/smalltalk/
There's the free/open Pharo ("Squak for grown-ups"): https://pharo.org/
(Recently featured on hn in the guise of smalltalk on truffle/graalvm https://news.ycombinator.com/item?id=21735782 )
And commercial offerings like Gemstone:
https://gemtalksystems.com/products/gs64/
The recently open sourced dolphin: http://www.object-arts.com/gettingstarted.html
You can run whatever code you want in those windows, but just by finishing the tutorial you'll have an idea of what you're getting into.
It's on my backlog of things to toy with, along with Prolog. And after playing with Objective-C long enough I quite enjoy the syntax.
As a teaser, consider a predicate Concat(X, Y, Z). In Prolog, capitalized identifiers are considered as unbound variables. There aren't return values; instead, you compute via unification.
So we can approximate the idea of a list concat funtion by entering something like `Concat([1, 2], [3, 4], X).` The Prolog evaluator will answer that X must be [1, 2, 3, 4] in order for the predicate to hold true. Looks like a standard function with a quirky way to provide an out param.
But these are predicates, not functions. Concat's internal logic defines a logical relation among the various values. This logic holds no matter which values you leave unbound. So here are some other things you get for free:
`Concat(X, Y, [1, 2, 3, 4]).` -- this will, in turn, iterate through all possible values of X and Y that make this predicate true. Thus Prolog will give us X: [], Y: [1,2,3,4]; but also X: [1], Y: [2,3,4]; and so on.
`Concat(X, [3, 4], [1, 2, 3, 4]).` -- this will give us X: [1, 2]; alternatively, it will give us the answer 'no' (sort of equivalent to false) if [3, 4] is not some tail of the provided list.
`Concat([1, 2], [3, 4], [1, 2, 3, 4]).` -- will give us "yes", for true.
This is a simple example, but now perhaps the potential power of Prolog is more visible. Imagine the possibilities -- for instance, a constructed grammar can tell you not only if a given candidate sentence is valid, but it can also (in theory) generate every single valid sentence in turn.
I've had an idea for a prolog side-project for a while, but just haven't learned enough yet to know how I could implement it. In my mind, reserving a slot in a calendar or otherwise finding the next best opportunity is a valid use-case.
Interestingly, I enjoy Objective-C despite its syntax.
"Cuis is a free Smalltalk-80 environment with a specific set of goals: being simple and powerful. It is also portable to any platform, fast and efficient."
It would be nice if there was a way to construct something similar around actor-based systems on the JVM.
bank move: 100 from: checkingAccount to: savingsAccount
Java: bank.move(100, checkingAccount, savingsAccount)bank.move(new Amount(100), new FromAccount(checkingAccount), new ToAccount(savingsAccount));
If you are really trying, you will be able to convey enough information so that your fellow programmer. will understand what you are trying to accomplish. Unfortunately, in practice, even Java Experts will convey to you that this style is wrong, classes and objects are " too expensive", primitive values are fine, and that this is too verbose.*
I learned about this style from Yegor Bugayenko's Book "Elegant Objects", go check it out. https://www.elegantobjects.org/
*) Yes, Java is verbose; not trying to defend that. I am very happy about keyword arguments in Python, for example. But, as soon as you have classes, ctors, objects, you can use them to add rich semantics into your code. Works with functional languages and other, better typesystems, too.
In Python, adding from=from_account, to=to_account to clarify a call is basically free; you don't have to change the callee and you don't have to change any other call sites.
very well said
bank.move(new Amount(100), AccountChannelFactory.create(AccountChannelFactory.CHECKING_CHANNEL, checkingAccount), AccountChannelFactory.create(AccountChannelFactory.SAVINGS_CHANNEL, savingsAccount));
bank.move( move: 100, from: checkingAccount, to: savingsAccount )
This option is also available for many of the other languages supported by the various JetBrains IDEs. bank balanceOf: (bank findAccount: #savings)
However, parens aren't necessary when a nested message takes no arguments, or uses a binary selector. eg: bank withdraw: 3 + 4 from: user checkingAccount
There are 3 messages here. 1. 3 + 4 (execute method + with argument 4)
2. user checkingAccount (getter with no arguments)
3. bank withdraw: <result of #1> from: <result of #2> bank.move(100, from=checkingAccount, to=savingsAccount)There have been some attempts to get Erlang running on the JVM but so far none that really made it worth adopting, and frankly I don't see how you could do it without losing everything that makes Erlang/OTP special.
Of course there are occasions where the name is unfortunate (typo, ambiguous name or incorrect term) but the docs could mention it and it would not be the end of the world.
It seems like most naming issues could be alleviated by allowing the developer to provide aliases, this way you can change the preferred parameter name while still keeping the old one around for backward compatibility.
They do in Swift's attempt at a keyword syntax.
You always have some concept of the API leak into the consumer. You either remember explicit names, or remember their positional pseudo-names 'first', 'second', 'third' etc. Unnamed arguments bring up more issues such as ordering consistency (see: PHP's many notorious cases), deprecation leaving 'holes' in argument lists, etc.
In Perl and many other languages we just provide a hash (aka dict) of key/value pairs. This lets the functions decide how to handle breaking changes which involves some annoying boilerplate but overall works fairly well. It's nicer with proper language support though.
The problem with named parameters as seen in Python is that then the name of the parameters become part of your APIs contract, making changes to your API harder. This has been remediated with patterns in Python 2 and explicit design in Python 3 by incorporating named only parameters so that you can't call them with both positional or named args.
So say I have a BankAccount class. In Python I would write a method to transfer to another account
def transfer(this, to, amount): ...
and invoke it as
account.transfer(other_account, 500)
In Smalltalk I would write
transferTo: account amount: n [ ... ]
and invoke it as
account transferTo: other_account amount: 500.
So it's not quite named arguments. It's that there isn't a distinction between name of function, argument name, and positional parameters. It just doesn't map to the function notation typical from mathematics.
Huh? How are APIs with mere positional parameters easier to change and more transparent to the callers when changed?
It is very deliberately inspired by Erlang and used widely in JVM applications.
foobar = send(actorA, GetDbVal(foo)) + send(actorB, GetDbVal(bar))
Instead you would need to construct some sort of mutable dictionary that keeps track of when it got both messages and when it does, have the actor finally add the stuff together. It’s not always so bad, but when you have actors that are interacting with a lot of other actors and querying many different resources, it becomes a problem. Akka tries to mitigate this problem with the state change function become(), which allows you to build a FSM to handle state transitions. While this helps a lot, it’s not a panacea.
I think I read somewhere that true multi-tasking is in the works for the JVM, so this hopefully won’t always be a problem. As it stands, it’s probably the biggest limitation of the platform atm.
Most of the issues around messaging come from waiting for answers. Hence callbacks, futures, async, retries, timeouts...
Good discussion of this back in 2012.[1]
He uses messaging to mean interfaces and protocols of communication.
Rank 1: Polymorphism. You can "send" a message to an object. You don't know or care exactly what the object does. Other than the polymorphism, the "message" behaves just like a function call. It will definitely be handled by your target object, and it will definitely return instantly with a response. The compiler might even be able to inline this function call.
As seen in: Pretty much every OOP language.
Rank 2: Messages as first-class values. It's possible to write a function that forwards all incoming messages to another object, based on some logic. The caller does not know what will happen to the message. However, the message can be expected to be handled instantly, and it may respond instantly (if it does have a response).
As seen in: Objective-C, Smalltalk
Rank 3: Asynchronous messaging. When sending an outgoing message, it might be placed in a queue and handled at a later date. The message does not return a result instantly, so agents must have some other mechanism (such as including a "reply to" address) to be able to talk back and forth. From the perspective of the caller, it's a bit like putting a message in a bottle and watching it drift off to sea.
As seen in: Erlang, message queueing libraries.[1]
“When I use a word,” Humpty Dumpty said, in rather a scornful tone, “it means just what I choose it to mean—neither more nor less.” “The question is,” said Alice, “whether you can make words mean so many different things.” “The question is,” said Humpty Dumpty, “which is to be master—that’s all.”
Arguments of a Smalltalk message can be "blocks" which are function-like objects. The receiver of a message carrying a block as argument can put the block into a list/queue and evaluate it any time in the future.
So that would seem to qualify as "asynchronous messaging" to me.
I think it is a limitation of "bottle-mail" that you can not easily reply to the message. Of course sometimes all you have is a bottle and a piece of paper and the sea around you :-)
Synchronized requesting x with request r (i.e. x!r) can be implemented as follows using a 2-phase commit protocol: x.synchronize[Implements Provider ⟦provide ↦ r⟧] so that after x has received a synchronize message with parameter Implements Provider ⟦provide ↦ r⟧, x can get r from the parameter using a provide message (cf. [Knabe 1992]). Synchronized sending x a request r (i.e. x!r) can be algebraically reduced (which is a primary requirement of communication in the π-calculus [Milner 1993]) because x is provided with r without arbitration by Implements Provider ⟦provide ↦ r⟧.
Synchronized requesting (i.e. x!r) has the following significant costs in time, communication bandwidth, and robustness by comparison with unsynchronized Actor requesting (i.e. x.r):
1. The requester must wait for the receiver’s provide message in order to provide request r.
2. After receiving a synchronize message, the receiver must wait for the request r to be provided (meanwhile holding up processing of other requests).
3. Both the requester and receiver must be online concurrently for communication to take place.
Unsynchronized requesting (i.e. x.r) cannot in general be reduced using an algebraic equation as in [Milner 1993] because in general, the request must go through arbitration in order to be received. Although algebraic reductions may be elegant mathematics, synchronized requesting is not widely used in large software systems because it is slower, uses more communication bandwidth, and is less robust than asynchronous requesting (especially for IoT).
According to [Milner 1993]: “An important task is to compare it {π-calculus based on algebraic reduction using synchronized requesting} with Hewitt's Actors; there are definite points of agreement where we have followed the intuition of Actors and also some subtle differences, such as the treatment of names {i.e., request providers}. More generally, the π-calculus is a formal calculus, while the Actors model, in spirit closer to the approach of physics, sets out to identify the laws {i.e. [Hewitt and Baker 1977] expanded into the uniquely categorical axiomatization in this article} which govern the primitive concepts of interaction.”
I’ve tried applying Actors to my C++ projects and always hit 2 issues in Actor model:
1) the need for synchronous access. As long as a single thread is accessing each Actor sequentially, then there is no issue with locking an Actor for synchronous access (barring deadlocks). I feel dirty locking Actors, but it solves many practical problems.
2) sharing resources requires a language which understands locks, since even Pony’s reference capabilities doesn’t solve real world performance issues. Ideally, each shared resource would have a paired lock whose usage the compiler verifies.
Both issues deviate from Actor model purity since they require locks, but I cannot resolve real world performance problems without them.
Unfortunately, C++ has many issues implementing Actors because of problems with regions of mutual exclusion with holes.
See the following: https://papers.ssrn.com/abstract=3418003
I've heard that Actors don’t have an identity, only addresses:
https://medium.com/@alex_karaberov/everything-you-always-wan...
>Each actor has an address. In various implementations an address can be a direct physical address (e.g. MAC address of the NIC), email, memory address, some id and so on. Multiple actors can have the same address, and one actor can have multiple addresses. There is a many-to-many relationship here. Address is not a unique identifier of the actor. Actors don’t have an identity, only addresses. So, when we step back and look at our conceptual Actor Model, we can see and use only addresses. We can not tell whether we have one actor or multiple ones even if we have one address, because it can be a proxy for the group of actors. All we can do with an address is send it a message. Address represents capability in the Actor Model. Mapping of addresses and actors is not part of the conceptual Actor Model although it is a feature of implementations.
So I was wondering if you're the same Carl E Hewitt as https://news.ycombinator.com/user?id=carlehewitt and as your user name professes, or if either or both of you are actors? ;)
And have you ever played Santa Claus?
Alexander Karaberov's article (linked above) is an important contribution.
You are correct that since an Actor can have many addresses, it is not clear what it would mean for an Actor to have an identity."
PS. Excellent SNL Santa :-)
There is a fundamental performance tradeoff between sequential vs concurrent computation, because concurrency always requires some form of communication. Locks are difficult to reason about, but allow for shared resources. I've found the actor model to be so useful because it allows me to trade the ability to share resources for a more intuitive form of communication, message passing.
Regarding your 1st point: Each actor is itself a sequential computer. You can't share an actor between threads any more than you could share a Turing Machine between threads. When designing actor systems I imagine a 1:1 mapping between threads and processors. The concurrency is happening in userspace.
Regarding your 2nd point: It's my feeling that current actor model implementations are only suited to a narrow range of problems characterized by massive concurrency, a lack of shared state, and flexible time requirements. If any of those aspects are missing, I've learned to look elsewhere for a solution.
Yes, but, that does not mean "async messaging" is not supported.
A Smalltalk "message" can return "nil" immediately in other words return nothing. A message-argument can be a "block" which is an executable function-like object, in fact very close to JavaScript function-closures.
So the recipient of a message carrying a block can at any time in the future ask the block to execute itself, with arguments, and thus produce async "replies", again much like with JavaScript "callbacks".
The arguments of a block can be blocks as well thus providing multi-way message passing.
What Smalltalk does not support out of the box is Distributed Programming, i.e. multiple machines communicating and working together passing messages among themselves. You will have to create your own servers and communications protocols to do that.
"I would say that a system that allowed other metathings to be done in the ordinary course of programming (like changing what inheritance means, or what is an instance) is a bad design. (I believe that systems should allow these things, but the design should be such that there are clear fences that have to be crossed when serious extensions are made.)"
∀[S:ActorSystem, P predicateOn Event<S>] // For every ActorSystem S and every predicate P on events of S
(∀[x:Primordial<S>] P[Creation[x]] // If P holds for the creation event of every primordial of S
⋀ ∀[e:FromOutside<S>] P[e] // and P holds for every event from outside S
⋀ ∀[e:Event<S>, e1:ImmediatelyFollows<e>] // and for every event of S
P[e]⇒P[e1]) // if P holds for the event, then P holds for each immediately following event
⇒ ∀[e:Event<S>] P[e] // then P holds for every event of S
See the following for more explanation: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3459566It's much nicer to get slowly walked through it with a textbook, but I'm blanking on the name of the book I used.
So, may I presume that the notation of (Prof) Carl Hewitt’s Direct Logic is consistant with that of First-Order Logic?
See the following:
https://www.textbookequity.org/Textbooks/Magnus_forallx.pdf
For the quantifiers, check out the Wikipedia page here:
https://en.m.wikipedia.org/wiki/Quantifier_(logic)
For deeper coverage, the section on first order logic here is quite good:
https://www.amazon.com/Introduction-Montague-Semantics-Synth...
So what might make a good metasystem? The ones we have are usually too specific, too concrete, the metasystem for this particular language, sort of like having a "types" rooted in concrete machine types (cough, C, cough). Smalltalk solved this for the type-system, with the root being something abstract, "Object". If you apply the same idea of creating a hierarchy to the metasystem(s), you come up with something along software-archtitectural ideas of component (procedures, methods, objects, filters, programs) and connector (calls, message-sends, pipes, variable access, ...).
And it turns out that this gives you parsimony, great power, and pretty darn good security of meaning.
It's not 100% the intention of Smalltalk messaging, but it's in the spirit of "objects as messages".
This idea gives rise to contexts.
It's the difference between graphs and trees.
It's the difference between linear logic and classical logic.
In some sense, heap allocated objects work like this. The main problem is that you can't relocate objects.
Rust's interior mutability is about this as well.
ECS[0] is about this.
Then don't. There are plenty of other submissions on HN, and if you don't see any you find worth discussing, submit some you do.
It has also been posted on HN for over 10 times, and a few times Alan Kay himself was commenting here too.
So it's not like everybody needs to read this particular "TFA" to jump into the discussion, or that it's that bad if they missed some specific reference you've made to the content of this particular article...