info
ActorScript™:
Industrial strength integration of local and nonlocal concurrency
for Client-cloud Computing
Carl Hewitt
http://carlhewitt.info
This paper is dedicated to Seymour Papert.
ActorScript™ is a general purpose programming language for implementing massive local and nonlocal
concurrency. It is differentiated from other concurrent languages by the following:
Identifiers (names) in the language are referentially transparent, i.e., in a given scope an identifier
always refers to the same thing.
Everything in the language is accomplished using message passing including the very definition of
ActorScript itself.
Binary XML and JSON are fundamental, being used for structuring both data and messages.
Functional and Logic Programming are integrated into general concurrent programming.
Advanced features such as eventing, co-routines, futures, serializers, sponsors, etc. can be defined and
implemented without having to resort to low level implementation mechanisms such as threads, tasks,
channels, queues, locks, cores, etc.
For ease of reading, programming can be displayed using a 2-dimensional textual typography (as is
often done in mathematics).
ActorScript attempts to achieve the highest level of performance, scalability, and expressiblity with a
minimum of primitives.
⊔
For example, Actors are allowed to pipeline the processing all the possible behaviors of S as
of messages. What this means is that in the course of DenoteS = i
iω ProgressionS (⊥S)
processing a message m1, an Actor can designate the
behavior to be used to process the next message, and then where ProgressionS is an approximation function that
in fact begin processing another message m2 before it has takes a set of partial behaviors to their next stage and
finished processing m1. Just because an Actor is allowed ⊥S is the initial behavior of S.
to pipeline the processing of messages does not mean that
it must pipeline the processing. Whether a message is In this way, the behavior of S can be mathematically
pipelined is an engineering tradeoff. characterized in terms of all its possible behaviors
(including those involving unbounded nondeterminism).
Locality and Security
Although DenoteS is not an implementation of S, it can be
Another important characteristic of the Actor model is
used to prove a generalization of the Church-Turing-
locality. Locality means that in processing a message: an
Rosser-Kleene thesis [Kleene 1943]:
Actor can send messages only to addresses for which it has
information by the following means: Enumeration Theorem: If the primitive Actors of a
1. that it receives in the message closed Actor System S are effective, then the possible
2. that it already had before it received the message outputs of S are recursively enumerable.
3. that it creates while processing the message. Proof: Follows immediately from the
Representation Theorem.
In the Actor model, there is no simultaneous change in
multiple locations. In this way it differs from some other The upshot is that concurrent systems can be
models of concurrency, e.g., the Petri net model in which represented and characterized by logical deduction
tokens are simultaneously removed from multiple locations but cannot be implemented. Thus, the following
and placed in other locations. practical problem arose:
How can practical programming languages be
The security of Actors can be protected in the following rigorously defined since the proposal by Scott and
ways: Strachey [1971] to define them in terms lambda
hardwiring in which Actors are physically calculus failed because the lambda calculus cannot
connected implement concurrency?
𝐟𝐢𝐫𝐬𝐭 𝐫𝐞𝐬𝐭
𝐂𝐫𝐞𝐚𝐭𝐞𝐄𝐧𝐯 , 𝐄𝐧𝐯
𝐁𝐢𝐧𝐝𝐢𝐧𝐠 𝐄𝐧𝐯 identifier
behavior behavior
implements Env implements Exp
𝐢 𝐞
Lookup< > Eval<𝐄𝐧𝐯> return eLookup<self>
𝐈𝐝𝐞𝐧𝐭𝐢𝐟𝐢𝐞𝐫
? ?
let Binding<firstIdentifier firstValue> = first,
cases i 𝐞
Match<value >
firstIdentifier return firstValue 𝐄𝐧𝐯 𝐄𝐧𝐯
not firstIdentifier return restLookup<i> return eBind<self value>
𝐢
Bind<𝐈𝐝𝐞𝐧𝐭𝐢𝐟𝐢𝐞𝐫 value>
𝐄𝐧𝐯
return CreateEnv(Binding<i value>, self )
Procedure invocations
Eventing
Eventing is implemented by providing events as elements
of sequences. Subscribing to events is accomplished by
consuming an event streams. For example a stream of the
leaves of a tree can be implemented as follows:
recipient performative
behavior
implements Exp
𝐜 𝐬
Communication<Request<Eval<e> > >⇛
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫 𝐒𝐩𝐨𝐧𝐬𝐨𝐫
{(recipient Eval<e> ) ⇚
Communication<
Request <(performative Eval<e>) c>
s>,
return}
SimpleAccount
Below SimpleCellt is defined that implement Cellt
For example the following expression creates a cell x with initial For example the following expression creates a cell x with initial
contents 5 and then concurrently writes to it with the values 7 and contents 5 and then concurrently writes to it with the values 7 and
9. 9.
𝐱 𝐱
let = new SimpleAccount(balance=5); let = new SimpleCellInteger(contents=5);
𝐀𝐜𝐜𝐨𝐮𝐧𝐭 𝐂𝐞𝐥𝐥𝐈𝐧𝐭𝐞𝐠𝐞𝐫
{xDeposit<7>, xDeposit<9>, xRead< > } {xWrite<7>, xWrite<9>, xRead< > }
The value of the above expression is 21 regardless of the order The value of the above expression is 5, 7 or 9.
of internal operations On the other hand sequential evaluation proceeds as follows:
𝐱
let = new SimpleCellInteger(contents=5);
𝐂𝐞𝐥𝐥𝐈𝐧𝐭𝐞𝐠𝐞𝐫
{xWrite<7>; xWrite<9>; xRead< > }
The value of the above expression is 9.
no response
behavior
implements Expression
Eval<e>
𝐎𝐮𝐭𝐜𝐨𝐦𝐞
return DidNotRespond< >
cases firstMethod
Method[ ]
𝐟𝐢𝐫𝐬𝐭𝐏𝐚𝐭𝐭𝐞𝐫𝐧 𝐟𝐢𝐫𝐬𝐭𝐁𝐨𝐝𝐲
“”
𝐏𝐚𝐭𝐭𝐞𝐫𝐧 𝐄𝐱𝐩𝐫𝐞𝐬𝐬𝐢𝐨𝐧
let {Communication<Request<message …>> = c;
𝐧𝐞𝐰𝐄
= firstPatternMatch<message e> };
𝐄𝐧𝐯
cases newE
null return ProcessCommunications(c, restMethods, e)
not null return firstBodyEval<newE>
Method[ ]
𝐟𝐢𝐫𝐬𝐭𝐏𝐚𝐭𝐭𝐞𝐫𝐧 𝐟𝐢𝐫𝐬𝐭𝐁𝐨𝐝𝐲
“”
𝐏𝐚𝐭𝐭𝐞𝐫𝐧 𝐄𝐱𝐩𝐫𝐞𝐬𝐬𝐢𝐨𝐧
let Communication<performative ?> = c;
𝐧𝐞𝐰𝐄
= firstPatternMatch<performative e>;
𝐄𝐧𝐯
cases newE
null return ProcessCommunication(c, restMethods, e)
not null return firstBodyEval<newE>
Method[ ]
𝐟𝐢𝐫𝐬𝐭𝐏𝐚𝐭𝐭𝐞𝐫𝐧 𝐟𝐢𝐫𝐬𝐭𝐁𝐨𝐝𝐲
“⇛”
𝐏𝐚𝐭𝐭𝐞𝐫𝐧 𝐄𝐱𝐩𝐫𝐞𝐬𝐬𝐢𝐨𝐧
𝐧𝐞𝐰𝐄
let = firstPatternMatch<c e>;
𝐄𝐧𝐯
cases newE
null return ProcessCommunications(c, restMethods, e)
not null return firstBodyEval<newE>
A Relay is the means by which a simple serializer coordinates with its behavior by packaging the outcome returned by the
behavior together with the original customer of the request and sending them in a Relayed package to the serializer.xvi
𝐜
Relay(s, )
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
behavior
𝐫
𝐑𝐞𝐬𝐩𝐨𝐧𝐬𝐞
cases r
𝐨
Returned< > {sReturned<Relayed<o c>>, no response}
𝐎𝐮𝐭𝐜𝐨𝐦𝐞
𝐞
Threw< > {cReturned<Threw<e>>, no response}
𝐄𝐱𝐜𝐞𝐩𝐭𝐢𝐨𝐧
𝐜𝐨𝐦
⇛
𝐂𝐨𝐦𝐦𝐮𝐧𝐢𝐜𝐚𝐭𝐢𝐨𝐧
cases com
𝐜𝐮𝐬𝐭
Communication<Request<? 𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
>…>
cases working
null {future currentRequest<Process<com> Relay(self, cust)> ,
no response also become SerializerBehavior(working=r)}
not null no response also become SerializerBehavior(pending=[pending com])
𝐨 𝐜𝐮𝐬𝐭
Communication<Returned<Relayed< 𝐎𝐮𝐭𝐜𝐨𝐦𝐞 𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
>>…>
cases o
ReturnedAlsoBecame<value next>
cases pending
[ ] {custReturned<value>, no response also become SerializerBehavior(current=next, working=null )}
𝐜
not [ ] let ([first rest]=pending, Communication<Request<? >…> = first)
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
{ custReturned<value> , future currentRequest<Process<first> Relay(self, c)> ,
no response also become SerializerBehavior(current=next, working=first, pending=rest)}
Returned<value>
cases pending
[ ] {{cReturned<value>, no response also become SerializerBehavior( working=null )}
𝐜
not [ ] let ([first rest]=pending, Communication<Request<? >…> = first)
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
{custReturned<value> , future currentRequest<Process<first> Relay(self, c)> ,
no response also become SerializerBehavior(working=first, pending=rest)}
Threw<e>
cases pending
[ ] {cThrew<e>, no response also become SerializerBehavior ( working=null )}
𝐜
not [ ] let ([first rest]=pending, Communication<Request<? >…> = first)
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
{ custThrew<e> , future currentRequest<Process<first> Relay(self, c)> ,
no response also become SerializerBehavior (working=first, pending=rest)}
DidNotRespond< >
cases pending
[ ] no response also become SerializerBehavior ( working=null )
𝐜
not [ ] let ([first rest]=communications, Communication<Request<? >…> = first)
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
{future currentRequest<Process<first> Relay(self, c)> ,
no response also become SerializerBehavior(working=first, pending=rest)}
DidNotRespondAlsoBecame<next>
cases pending
[ ] no response also become SerializerBehavior (current=next, working=null )
𝐜
not [ ] let (<first rest]=pending, Communication<Request<? >…> = first)
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
{future currentRequest<Process<first> Relay(self, c)> ,
no response also become SerializerBehavior(current=next, working=first, pending=rest)}
futuresponsor expression
behavior
implements Expression
𝐜𝐮𝐬𝐭
𝐑𝐞𝐪𝐮𝐞𝐬𝐭 < 𝐄𝐯𝐚𝐥 < 𝐞 > >
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
let f new FutureBehavior(hasResponse= false,
repackagerThenResponse = r,
customers=[ ],
derivatives = [ ] )
r new Repackager(f)),
note that f and r are defined in terms of each other
{expression ⇚
Communication<Request<Eval<e> f>sponsor>,
note that because of the use of comma (,) above that
concurrency is not forced in using a future
cust Returned< f >,
no response}
𝐜𝐮𝐬𝐭
Repackager( )
𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
serializer
𝐫
{custReturned<Responded<r> >, no response}
𝐑𝐞𝐬𝐩𝐨𝐧𝐬𝐞
𝐫 𝐜𝐮𝐬𝐭𝐨𝐦𝐞𝐫𝐬 𝐝𝐞𝐫𝐢𝐯𝐚𝐭𝐢𝐯𝐞𝐬
ProcessCustomersAndDerivatives( , , ) 𝑣𝑜𝑖𝑑
𝐑𝐞𝐬𝐩𝐨𝐧𝐬𝐞 𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫∗ 𝐃𝐞𝐫𝐢𝐯𝐚𝐭𝐢𝐯𝐞∗
{ProcessCustomers(r, customers),
return ProcessDerivatives(r, derivatives)}
𝐫 𝐜𝐮𝐬𝐭𝐨𝐦𝐞𝐫𝐬
ProcessCustomers( , ) 𝐯𝐨𝐢𝐝
𝐑𝐞𝐬𝐩𝐨𝐧𝐬𝐞 𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫 ∗
cases customers
[ ] return
[first rest]
{cases r
Returned<v> first Returned<v>
Threw<e> firstThrew<e>,
return ProcessCustomers(r, rest)}
𝐫 𝐝𝐞𝐫𝐢𝐯𝐚𝐭𝐢𝐯𝐞𝐬
ProcessDerivatives( , ) 𝐯𝐨𝐢𝐝
𝐑𝐞𝐬𝐩𝐨𝐧𝐬𝐞 𝐃𝐞𝐫𝐢𝐯𝐚𝐭𝐢𝐯𝐞 ∗
cases derivatives
[ ] return
[first rest]
{first Forward<r>,
return ProcessDerivatives(r, rest)}
A CutOffSponsor that can be told to cut off resources by sending it a CutOff message can be implemented as follows:
𝐬
CutOffSponsor( ) 𝐒𝐩𝐨𝐧𝐬𝐨𝐫
𝐒𝐩𝐨𝐧𝐬𝐨𝐫
serializer
𝐚𝐥𝐫𝐞𝐚𝐝𝐲𝐂𝐮𝐭𝐎𝐟𝐟
𝐁𝐨𝐨𝐥𝐞𝐚𝐧
𝐫
𝐑𝐞𝐬𝐨𝐮𝐫𝐜𝐞𝐑𝐞𝐪𝐮𝐞𝐬𝐭
cases alreadyCutOff
true throw Refused<>
false return sr
𝐫
𝑛𝑜𝑡 𝐑𝐞𝐬𝐨𝐮𝐫𝐜𝐞𝐑𝐞𝐪𝐮𝐞𝐬𝐭
cases r
CutOff<> return also become (alreadyCutOff=true)
not CutOff<> return sr
{discard ; value }
The expressions discard and value are evaluated sequentially, i.e., evaluation of value does not begin before discard
has responded with one of the following:
3. A return value that is discarded and then value is evaluated to produce the response (if any) for {discard ;
value }..
4. An exception that is rethrown to produce the response for {discard ; value } and the evaluation of value is
never begun.
𝐜𝐮𝐬𝐭 𝐞 𝐯𝐚𝐥𝐮𝐞𝐄𝐱𝐩𝐫𝐞𝐬𝐬𝐢𝐨𝐧
SemicolonCustomer(𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫 𝐄𝐧𝐯 𝐄𝐱𝐩
) 𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫
behavior
implements Customer
Thrown<exception>
{cust Thrown<exception>, no response}
Returned<v>
{valueExpression Request<Eval<e> cust>, no response}
{discard , value }
Evaluation of expressions discard, and value can be interleaved.. i.e. there are no restrictions on how
discard and value are evaluated except that:
4. A return value of discard is discarded and the response (if any) of value passed back for
{discard , value }.
5. If the evaluation of discard or value generates an exception then any such exception can be
rethrown for
{discard , value }.
6. If there is no response from evaluating discard or value , then evaluation of the other might never
start.
discardCustomer(valueCust cutOffSponsor)
behavior
Thrown<e> {cutOffSponsor CutOff<>; valueCust Thrown<e>, no response}
Returned<v>
{valueCust Discarded<>; no response}
valueCustomer(cust commaSponsor)
serializer
𝐚𝐥𝐫𝐞𝐚𝐝𝐲𝐃𝐢𝐬𝐜𝐚𝐫𝐝𝐞𝐝
𝐁𝐨𝐨𝐥𝐞𝐚𝐧
𝐡𝐚𝐯𝐞𝐕𝐚𝐥𝐮𝐞
𝐁𝐨𝐨𝐥𝐞𝐚𝐧
value
Thrown<e>
{commaSponsor CutOff<>; cust Thrown<e>; no response}
Returned<v>
{commaSponsor CutOff<>;
cases alreadyDiscarded
true {cust Returned<v>, no response}
false no response also become(haveValue=true, value=v)}
Discarded<>
cases haveValue
True {cust Returned<value>, return}
False no response also become(alreadyDiscarded=true)
{discard || value }
behavior
𝐞 𝐜𝐮𝐬𝐭 𝐬
Communication<Request<Eval<𝐄𝐧𝐯 > 𝐂𝐮𝐬𝐭𝐨𝐦𝐞𝐫 > 𝐒𝐩𝐬𝐨𝐧𝐬𝐨𝐫
>⇛
let (cutOffSponsor= new (CutOffSponsor(s )) (alreadyCutOff=false)
valueCust= new (valueCustomer(cust cutOffSponsor))
(alreadyDiscarded=false, haveValue=false),
discardCust = discardCustomer(valueCust cutOffSponsor))
{discard ⇚ Communication<Request<Eval<e discardCust> cutOffSponsor> ||
value ⇚ Communication<Request<Eval<e valueCust> cutOffSponsor >,
no response}
discardCustomer(valueCust cutOffSponsor)
behavior
Thrown<e> {cutOffSponsor CutOff<>; c Thrown<e>, no response}
Returned<v> {valueCust Discarded<>; no response}
valueCustomer(cust commaSponsor)
serializer
𝐚𝐥𝐫𝐞𝐚𝐝𝐲𝐃𝐢𝐬𝐜𝐚𝐫𝐝𝐞𝐝
𝐁𝐨𝐨𝐥𝐞𝐚𝐧
𝐡𝐚𝐯𝐞𝐕𝐚𝐥𝐮𝐞
𝐁𝐨𝐨𝐥𝐞𝐚𝐧
value
Thrown<e> {commaSponsor CutOff<>; cust Thrown<e>; no response}
Returned<v>
{commaSponsor CutOff<>;
cases alreadyDiscarded
True {cust Returned<v>, no response}
False no response also become (haveValue=True, value=v)}
Discarded<>
cases haveValue
True {cust Returned<value>, return}
False no response also become(alreadyDiscarded=true)
𝑝𝑟𝑜𝑣𝑒𝑛𝑎𝑛𝑐𝑒
?𝑡ℎ𝑒𝑜𝑟𝑦 goal
𝑝𝑟𝑜𝑣𝑒𝑛𝑎𝑛𝑐𝑒
├𝑡ℎ𝑒𝑜𝑟𝑦 sentence behavior
implements Expression
behavior Eval<e>
implements Expression return
Eval<e> (theory Eval<e>) ? <provenance goal e>
return
(theory Eval<e>) ├ <sentenceEval<e>
provenanceEval<e> > 𝑝𝑟𝑜𝑣𝑒𝑛𝑎𝑛𝑐𝑒
?𝑡ℎ𝑒𝑜𝑟𝑦 goal then expression
Establish goal with provenance to be proved in
theory and when established evaluate expression.
Forward Chaining
𝑝𝑟𝑜𝑣𝑒𝑛𝑎𝑛𝑐𝑒
├𝑡ℎ𝑒𝑜𝑟𝑦 sentence expression
𝑝𝑟𝑜𝑣𝑒𝑛𝑎𝑛𝑐𝑒
Forward Chaining: when a sentence matches ?𝑡ℎ𝑒𝑜𝑟𝑦 goal then expression
sentence with provenance in theory, evaluate behavior
expression. implements Expression
Eval[e]
return
𝑝𝑟𝑜𝑣𝑒𝑛𝑎𝑛𝑐𝑒
├𝑡ℎ𝑒𝑜𝑟𝑦 sentence expression (theory Eval<e>)
?<provenance goal expression e>
behavior
implements Expression
Eval<e>
return Illustration:
(<theory> Eval<e>) {
?<sentence provenance expression e> ├𝑇
𝐓𝐡𝐞𝐑𝐞𝐩𝐮𝐛𝐥𝐢𝐜
Human[Socrates] ;
𝐓𝐡𝐞𝐑𝐞𝐩𝐮𝐛𝐥𝐢𝐜
├𝑇 Human[Plato] ;
Illustration: 𝐩
? 𝑇 Human [h] then Collect(h) }
𝐩 𝐇𝐮𝐦𝐚𝐧𝐈𝐧𝐟𝐞𝐫𝐬𝐌𝐨𝐫𝐭𝐚𝐥(𝐩)
├ 𝑇 Human[x] ├ 𝑇 Mortal[x] will result in concurrently calling Collect with the
arguments Socrates and Plato
Logic Programmingxxx
Robert Kowalski developed the thesis that “computation
could be subsumed by deduction” [Kowalski 1988a] that
he states was first proposed by Hayes [1973] in the form
“Computation = controlled deduction.” [Kowalski 1979]
This thesis was also implicit in one interpretation of
Cordell Green‟s earlier work [Green 1969]. Kowalski
forcefully stated:
In concrete terms, typically we cannot observe the details McDermott and Sussman in [1972] developed the Lisp-
by which the arrival order of messages determined. based language Conniver based on “hairy control
Attempting to do so affects the results and can even push structure” (first introduced in [Landin 1965]) that could
the indeterminacy elsewhere. Instead of observing the implement non-chronological backtracking that was more
internals of arbitration processes, we await outcomes. The general than the chronological backtracking in Planner.
reason that we await outcomes is that we have no Hewitt and others were skeptical of hairy control structure.
alternative because of indeterminacy. Pat Hayes [1974] remarked:
Their [Sussman and McDermott] solution, to give the
Reasoning about Actors user access to the implementation primitives of
Planner, is however, something of a retrograde step
The principle of Actor induction is:
(what are Conniver's semantics?).
1. Suppose that an Actor x has property P when it is
created
In the 1960‟s at the MIT AI Lab a remarkable culture grew
2. Further suppose that if x has property P when it
up around “hacking” that concentrated on remarkable feats
processes a message, then it has property P when
of programming.xxxi Growing out of this tradition, Gerry
it processes the next message.
Sussman and Guy Steele decided to try to understand
3. Then x always has the property P.
Actors by reducing them to machine code that they could
understand and so developed a “Lisp-like language,
In his doctoral dissertation, Aki Yonezawa developed
Scheme, based on the lambda calculus, but extended for
further techniques for proving properties of Actor systems
side effects, multiprocessing, and process
including those that make use of migration. Russ Atkinson
synchronization.” [Sussman and Steele 1975]
developed techniques for proving properties of Serializers
Unfortunately, their reductionist approach included