Hewitt [2006] presented a prototype actor programming language in the sense that it directly expresses important aspects of the behavior of actors.
Messages are expressed in XML using the notation
:<tag>[<element>1 ... <element>] for
- “<”<tag>“>” <element>1 ... <element>n “<”/<tag>“>”
The semantics of the programming language are defined by representing each program construct as an actor with its own behavior. Execution is modeled through the passing of Eval messages among these constructs during runtime.
Each Eval message has the address of an actor that acts as an environment with the bindings of program identifiers. Environment actors are immutable, i.e., they do not change.
When Request[Bind[identifier value] customer] is received by an actor environment, a new environment actor is created such that
when the new environment actor receives
Request[Lookup[identifier’] customer’] then if identifier is the same as identifier’ send customer’ Returned[value], else send Environment
Request[Lookup[identifier’] customer’].
The above builds on an actor EmptyEnvironment which
when it receives Request[Lookup[identifier] customer], sends customer Thrown[NotFound[identifier]].
When it receives a Bind request EmptyEnvironment acts like Environment above.
The prototype programming language has expressions of the following kinds:
- <identifier>
- When Request[Eval[environment] customer] is received, send environment Request[Lookup[<identifier>] customer]
- send <recipient> <communication>
- When Request[Eval[environment] customer] is received, send <recipient> Request[Eval[environment] evalCustomer1] where evalCustomer1 is a new actor such that
- when evalCustomer1 receives the communication Returned[theRecipient], then send <communication>
- Request[Eval[environment] evalCustomer2] where evalCustomer2 is a new actor such that
- when evalCustomer2 receives the communication Returned[theCommunication], then send theRecipient theCommunication.
- <recipient>.<message>
- When Request[Eval[environment] customer] is received, send <recipient> Request[Eval[environment] evalCustomer1] such that
- when evalCustomer1 receives the communication Returned[theRecipient], then send <message> Request[Eval[environment] evalCustomer2] such that
- when evalCustomer2 receives the communication Returned[theMessage], then send theRecipient
- Request[theMessage customer]
- receiver ... <pattern>i <expression>i ...
- When Request[Eval[environment] customer] is received, send customer a new actor theReceiver such that
- when theReceiver receives a communication com, then create a new bindingCustomer and send environment
- Request[Bind[<pattern>i com] bindingCustomer] and
- if bindingCustomer receives Returned[environment’], send <expression>i
- Request[Eval[environment’]]
- otherwise if bindingCustomer receives Thrown[...], try <pattern>i+1
- behavior ... <pattern>i <expression>i ...
- When Request[Eval[environment] customer] is received, send customer a new actor theReceiver such that
- when theReceiver receives Request[message customer’], then create a new bindingCustomer and send environment
- Request[bind[<pattern>i message] customer’] and
- if bindingCustomer receives Returned[environment’], send <expression>i
- Request[Eval[environment’] customer’]
- otherwise if bindingCustomer receives Thrown[...], try <pattern>i+1
- {<expression>1, <expression>2}
- When Request[Eval[environment] customer] is received, send <expression>1 Request[Eval[environment]] and concurrently send <expression>2 Request[Eval[environment]] customer].
- let <identifier> = <expression>value in <expression>body
- When message[Eval[environment] customer] is received, then create a new evalCustomer and send <expression>value
- Request[Eval[environment] evalCustomer1.
- When evalCustomer receives Returned[theValue], create a new bindingCustomer and send environment
- Request[bind[<identifier> theValue] bindingCustomer]
- When bindingCustomer receives Returned[environment’], send <expression>body Request[Eval[environment’] customer]
- serializer <expression>
- When Request[Eval[environment] customer] is received, then send customer Returned[theSerializer] where theSerializer is a new actor such that communications sent to theSerializer are processed in FIFO order with a behavior actor that is initially <expression>.Eval[environment] and
- When communication com is received by theSerializer, then send the behavior actor Request[com customer’] where customer’ is a new actor such that
- when customer’ receives Returned[theNextBehavior] then theNextBehavior is used as the behavior actor for the next communication received by theSerializer.
An example program for a simple storage cell that can contain any actor address is as follows:
- Cell ≡
- receiver
- Request[Create[initial] customer]
- send customer Returned[serializer ReadWrite(initial)]
The above program which creates a storage cell makes use of the behavior ReadWrite which is defined as follows:
- ReadWrite(contents) ≡
- behavior
- Request[read[] customer]
- {send customer Returned[contents], ReadWrite(contents)}
- Request[write[x] customer]
- {send customer Returned[], ReadWrite(x)}
The above behavior is pipelined, i.e., the behavior might still be processing a previous read or write message while it is processing a subsequent read or write message..
For example, the following expression creates a cell x with initial contents 5 and then concurrently writes to it with the values 7 and 9.
- let x = Cell.Create[5] in {x.write[7], x.write[9], x.read[]}
The value of the above expression is 5, 7 or 9.