How the Runtime Casts Actors
Sophie now has syntax for user-defined actors. The resolver passes also (seem to) pass muster on actors. I’m anxious to see this work, so I’ll skip type-checking for the moment and start with the runtime. I will come back to check the types of actors some other time.
Recap of the Runtime
Sophie’s interpreter is currently a super-simple tree-walk.
The file runtime.py has the following major sections:
A bunch of
_eval_thisand_eval_thatwhich express the operational semantics of evaluating expression.The functions
forceand_strict, along withclass Thunk, most of the mechanism of lazy-evaluation.Classes
Closure,Primitive, andConstructor, all subclassingFunction, which represent the runtime manifestation of callable values that take at least one parameter.Classes
Nop,CompoundAction,BoundMessage, andTaskAction, implementingActionby way of the.perform()method. These provide operational semantics for observable behavior. These are considered runtime values in a certain sense.Subclasses of
Message:ClosureMessageandBoundMethod. These provideapplyas in you’ll provide some arguments and then get something ready to send. The last doubles as anActionfor when the underlying method (behavior?) does not take arguments. So again these are run-time values, and may be seen structurally asFunction. Indeed,Messagedoes inherit fromFunction.The structures by which to interface with the multithreading runtime:
PlainTaskandParametricTaskare already well-used. In contrast,UserDefinedActoris partially sketched out but needs a bit more work.And a few bits of glue-code to tie it all together.
The first hint that this was going to be confusing came from the names BoundMessage and BoundMethod.
Creating Instances
The semantics around creating an actor-instance are that there are new-actor phrases
(joining a name to a template-expression) which may be found as part of a do-block.
The current _eval_do_block returns a CompoundAction because there’s a phase
difference between figuring out what work to do, and actually doing the work.
Note
This phase-difference allows one to treat an action as data without any special syntax. Some languages require an explicit call-procedure form, such as the empty parens. In Sophie, it’s meant to be clear from context.
So that means I’ll have to look at how CompoundAction behaves.
Its perform method presently uses the passed-in dynamic_env which reflects the
local static scope wherever the do-block actually appeared.
I’ll adjust CompoundAction::perform to create a subordinate activation-record
and therein assign names per its NewAgent entries – if it has any.
Thanks to the possibility of shadowing, I cannot simply use the outer activation record.
(Maybe I should disallow shadowing? But that issue should be about ergonomics, not hacks.)
The idea is to evaluate the template-expression in the scope
given by CompoundAction’s self._dynamic_env,
use the resulting template to create an instance, and assign the instance to the name.
New Run-Time Types
I’ll need to distinguish actor-instances from actor-templates and actor-classes.
An actor-instance must have a private copy of local state, a vtable. Both can just be dictionaries.
An actor-template must have the information necessary to create an actor-instance. However, it lacks a mailbox or any of the interface necessary to participate in the message-passing and task-scheduling machinery.
An actor-class is sort of a function that creates actor-templates. If syntactically the actor is stateless, then the actor’s name should bind directly to a suitable template that just doesn’t define any special state. But if the actor expects private state, then we must bind the name to something akin to a record-constructor. When called with arguments, it produces a template.
Executive Adjustments
There is a function _prepare in executive.py which must be adjusted.
Previously it only declares a placeholder slot for UserAgent symbols.
It should rather assign either class or template as appropriate.
This could in principle also be solved in runtime.py : _eval_lookup,
but I’d rather deal with it in the same place as record-constructors.
Early on I’d considered allowing actor-templates to be defined anywhere,
rather than only as top-level declarations. If I’d done that,
then adjusting _eval_lookup would be more appropriate,
and also actors would require a static-scope pointer.
You can make a case for doing it either way, but right now, this is the way.
Strictness and Volatility
Probably, neither the state of an actor nor the content of a message should be a thunk directly. This is easy to accomplish by forcing the arguments before constructing actors and messages. (Indeed, the runtime already does this.) However, there’s also the possibility to pass records around. In this case, it’s probably not practical to require strictness all the way down.
There is a tricky other side to the strictness coin. Actor state is mutable, so when you write down an expression involving that state, you want to know which version of the state you’re writing about.
- Hazard
Suppose we pass current state into a function that builds a structure from its arguments. And suppose we either store that structure into a message or update state based upon it. If that structure contains a thunk which refers back to mutable parts of
self, then the ultimate reader could get the wrong data. That would be a pernicious data race.- Chosen Solution
I shall add a bottom-up pass over the syntax of value-expressions. The idea is to mark them as being “volatile” if they contain a volatile sub-expression, with any reference to
selfbeing the base case of volatility.The
delayfunction then gets modified so that it never makes a thunk of a volatile expression, but rather evaluates it eagerly.- Consequence
This will appear much like having strict/eager evaluation within the scope of actor behaviors. It’s a subtle difference, but I think it will work out sensibly. There can be non-volatile sub-expressions to a volatile expression, and these may still get the laziness treatment.
Furthermore, expressions outside of an actor’s behaviors cannot be volatile, so Sophie remains lazy in all the pure-functional parts.
- Alternative Idea
I could instead give actors a “become” operation and forego assignment to fields. The only sensible conception would be if every behavior “returns” the next subsequent state of the actor. And frankly, that gets tiresome.