Actors

The decision everything follows from

An actor never pulls from its mailbox. Its methods are its handlers, and a handler answers the next state:

Actor subclass: #Cart fields: (items : Array[Str]).

Cart class >> new   = Cart fields: { items: #[] }.
Cart >> add: item   = self with: { items: items copyWith: item }.
Cart >> count       = self reply: items size.

Nothing captures a continuation, so nothing needs the fibers, generator trampolines or effect handlers an Erlang-style receive would force onto JavaScript. Where a handler has to wait, JavaScript's own await is the continuation it already has.

The whole mailbox turn is: call the handler, await it if it answered a promise, take the next state out of what came back, and render if the state moved. That is where re-rendering is decided, and it is why there is nothing to memoise.

Sending

c := Actor spawn: Cart new.
c tell add: '六角ボルト'.              "does not wait"
(c ask count) forced printNl.          "waits for the reply"

tell answers nothing. ask answers a Future, and forced is where the waiting happens — inside a handler that is where this actor's own mailbox pauses, and nowhere else's.

Messages from one actor to another arrive in the order they were sent. An async handler holds its mailbox until it settles, so what arrives while it waits is processed afterwards, in order.

Waiting without stopping

forced is not the only way to consume a Future. A handler can hand the reply back to itself as an ordinary message and return immediately:

Screen >> load = (
  (Http get: 'orders.json') then: [:r | me ref tell loaded: r].
  self ).

Screen >> loaded: r = self with: { rows: ... }.

Written this way the screen keeps answering while the request is in flight. This is the shape used everywhere in the demos, including for a WebSocket, where the callback does nothing but me ref tell arrived:.

When something goes wrong

There is no try. A handler that raises kills its actor, and whoever is watching is told:

Boss >> start = ( me monitor: worker. self ).
Boss >> down: d = self with: { log: log copyWith: d actor name , ' が落ちた' }.

monitor: hears about it; link: dies with it. A supervisor is an ordinary actor that spawns a replacement when it hears down:.

become: is a type state

An actor can turn into a different class, which is what replaces selective receive: a phase is a class rather than a filter over the mailbox.

Door >> open = self become: (OpenDoor fields: {}).

What the reference was typed at decides what must be preserved. Spawned plainly, a reference is ActorRef[Door] and accepts everything Door accepts, so every phase must keep all of it. Spawned as: a protocol, the reference only ever accepts that protocol, and each phase may carry handlers of its own that nobody outside can reach. Both are checked at compile time, per phase.

The ring that is refused

Two actors waiting on each other's reply are stuck for good, and nothing raises: no actor died, so no monitor fires. So ask follows the wait chain and refuses on the spot if it arrives back:

Pong#2 asked Ping#3, which is waiting on it — nobody can take a turn

What it finds is certainly a cycle. The boundary is stated: the chain lives on this thread, so a ring that crosses to a worker is not found.