Collections as behavior
This tutorial covers use of ordinary Smalltalk objects not inheriting
from ActorBehavior as behavior objects for
Actor process
instances. It focuses primarily on the problems that might arise in
such situations.
An OrderedCollection actor
Let’s experiment with using an OrderedCollection as behavior for an
Actor.
a := Actor bootProxy: [ OrderedCollection new ].
We are now able to send messages to our actor, as usual:
a add: 3.
a add: 4.
If we explore our actor using “explore it” on a in our workspace, we
see the state of the actor is as follows:

As expected, we see an OrderedCollection(3 4) as the behavior
object.
Interactions with actors default to asynchronous:
a sum. "a Promise"
But we can wait for them to complete as usual if we like:
a sum wait. "7"
However, a surprise is in store for us if we try to ask for the size
of the OrderedCollection.
a size.

What is happening here is that ActorProxy inherits from Object,
which implements size. Instead of forwarding our request to the
Actor (and hence to its behavior), the ActorProxy tried to answer
size itself.
This is one of the reasons for the different
interaction patterns that
ActorProxy objects offer. Use of
sync here allows us to ensure the
request is forwarded to the Actor correctly.
a sync size wait. "2"
Other methods that trigger this class of difficulty include at: and
at:put:, along with all the other methods defined on class Object.
Another surprise lies in wait when it comes to exceptions signaled by an object as part of its normal interface.
Running removeFirst twice works just fine. The third request will
signal an empty collection error:
a removeFirst wait. "3"
a removeFirst wait. "4"
a removeFirst wait. "(results in the following window)"
 on an OrderedCollection() (terminated)).png)
Clicking on “Debug” allows us to find out what went wrong. In the
lower-left panel on the debugger, when the topmost
(ActorPromise(Promise)>>signalErrorValue) context is selected, is an
errorValue variable. Selecting that variable shows the underlying error
in the adjacent panel:
 on an OrderedCollection() (terminated)) (debugger).png)
The main takeaway from this is that, since the behaviour object of the actor signaled an uncaught exception, the entire actor has been terminated and is no longer able to reply to requests.
If we ask for its sum again, we’re notified that the actor has
terminated, with the “collection is empty” exception that caused it to
stop.
At this point, there is no recovery; termination of an actor is final.
A Dictionary actor
Similarly, we can try using a Dictionary as an actor’s behavior
object.
d := Actor bootProxy: [ Dictionary new ].
d async at: 1 put: 2. "nil"
d async at: 3 put: 4. "nil"
As before, ordinary methods work just fine.
To avoid the problems with exceptions as part of an object’s normal interface, we can avoid those parts of the interface, and instead use alternatives that allow us to execute specific pieces of code when otherwise an exception would be signalled:
(d at: 1 ifAbsent: [ Actor callerError: 'No such key' ]) wait. "2"
(d at: 11 ifAbsent: [ Actor callerError: 'No such key' ]) wait. "an error"
(d removeKey: 1 ifAbsent: [ Actor callerError: 'No such key' ]) wait. "works once..."
(d removeKey: 1 ifAbsent: [ Actor callerError: 'No such key' ]) wait. "...but not twice."
By using Actor class >> #callerError:, we avoid having an uncaught
exception be thrown. Instead, only the promise associated with the
request is rejected, causing an Error to be signalled in the
caller, but leaving the Dictionary actor itself unharmed. Sending
future requests to the dictionary actor will continue to work.
Our strategy of using methods like at:ifAbsent: instead of at:,
and removeKey:ifAbsent: instead of removeKey:, has allowed us to
avoid killing the whole actor, but at the cost of having the
ifAbsent: block execute in the wrong context. That is, if it runs,
it will be called from a context where self is the Dictionary, and Actor
current and Actor me denote the actor that the client knows as d.