SubScript Actors

Introduction

Programming the control flow of actors in Java or Scala is relatively hard, just like with GUIs. In both cases events arrive as calls to listeners; these listeners then perform some actions; they may change a state and change the set of interesting events. After this call-back-call the next one will occur at some point.

With SubScript the control flow may be inverted; scripts treat events and internal actions in an equal way. State is largely maintained by these scripts. This page describes how SubScript does this on top of Akka actors. Under the hood about the same things happen as in plain Scala versions of Akka actors: there are still partial functions listening to incoming data. This way the full power of the Akka framework remains available.

 

Example 1: Ping-Pong

The following pair of actors would exchange some “Ping” messages, terminated by “Stop”:

class Ping(another: ActorRef) extends Actor {
  another ! "Ping"
  another ! "Ping"
  another ! "Stop"
}

class Pong extends SubScriptActor {
  script live = <<"Ping">> ... / <<"Stop">>
}

The brackets <<….>> denote a message handler; on the inside there is essentially a partial function, like in the usual receive methods of actors. Multiple of such message handlers may be active at the same time; these handlers are considered when Akka gives an message to the actor for handling. If any such active message handler has a partial function that is defined for the message, it will handle it, and the actor’s receive method will not be called.

Message handlers may be more complicated than the presented ones. The general form is:

<< case p_1 => scalaCode_1 ==> scriptExpr_1
   case p_2 => scalaCode_2 ==> scriptExpr_2
   ....
   case p_n => scalaCode_n ==> scriptExpr_n >>

This is much like a Scala partial function that is usually returned by an actor’s receive method. The long arrow with scriptExpr_1 etc is new. This specifies the behavior that holds after the corresponding message handling.

Because << and >> act as a bracket pair, the names of parameters, values and variables before the long arrow are available in the scriptExpr. It may also safely use sender, which is a local value silently copied from the class variable with the same name. Simpler forms are possible because of the next rules:

  • The sections that are proceeded by the arrows may be omitted.
  • If there is only one case then that tag may be omitted.

 

Example: a Data Store with a proxy

For a more advanced example, imagine a data store with a proxy and a backend. A client may send information requests to the proxy. This sends the request on to the backend store. The latter will reply the requested information.

 

Based on this, the proxy sends a new request for detailed information to the backend store. After the proxy has received this detailed information it sends all received information back to the original client. In plain Scala+Akka a state machine would typically specify the behavior of the data store proxy:

class DataProxy(ds: ActorRef) extends Actor {
  def waitingForRequest = { case req: Request =>
                                  ds ! req
                                  context become waitingForData(sender)
                          }
  def waitingForData(requester: ActorRef) =
                          { case data: Data =>
                                 ds ! DtlRequest(data)
                                 context become waitingForDetails(requester, data)
                          }
  def waitingForDetails(requester: ActorRef, data: Data) =
                          { case dtls: Details =>
                                 requester ! (data, dtls)
                                 context become waitingForRequest
                          }
}

 

The states have meaningful names, but the control flow may be confusing since the program seems to jump between these states. A SubScript solution may apply nested message handlers:

live = << req: Request => val client = sender; ds ! req
                      ==> << data: Data
                          => ds ! DtlRequest(data)
                         ==> << dtls: Details => client ! (data,dtls) >>
                          >>
       >>
       ...

 

This is shorter than the plain Scala solution. The ability to nest message handlers is powerful, but that comes with a price: like with regular control structures, each deeper level makes the code more complex, in general.

With dataflow operators the code may be flattened and shortened:

live = << req : Request ==> {ds ? req}
       ~~(data: Data   )~~> {ds ? DtlRequest(data)}
       ~~(dtls: Details)~~> {:sender!(data,dtls):}
       >>
       ...

Here the message to the backend store are sent using the ? operator: in Akka this sends a message using a new, shortlived actor, and asynchronously awaits the answer from the addressee. The ? operator returns a future that completes when the answer has been received. These future value expressions are enclosed inside normal brace pairs, which do not denote actions but just Scala values (Note: this actually reflects a change in the SubScript syntax as of mid 2015; normal code fragments had been enclosed between plain braces, but this will become {!!}).

Just like with delay and search in the Twitter example, these futures are converted using an implicit script. The brace pairs are needed to parse these as Scala expressions rather than process expressions.

{:sender!(data,dtls):} is a tiny code fragment that sends the data including the details back to the original sender of the request. In a plain Akka sender is a class variable that gets overridden each a new message arrives. Here inside the message handling brackets, <<>>, that class variable is copied into a local script variable with the same name, so the name sender may be safely be used later inside the message handler.

Leave a Reply