The previous post described the ground work, getting geared up to employ the power of macros within Akka and doing type calculations. This post focuses on the user-visible features which are enabled by the macro machinery, presenting a new face for actor communication. The untyped “receive” method is replaced by declaration of any number of different input channels (though the single-mailbox principle is kept); the untyped “!” operator is replaced by different ones to avoid confusion.
Let me apologize at this point to our Java users. We try to keep both sides of Akka on par wherever possible but this is a feature which is fundamentally impossible to provide in Java. Please do not look at TypedActors now for compensation, they do not solve the same issues and their restrictions still apply. I am sincerely sorry.
With that out of the way, let us go in medias res:
This little snippet contains most of what typed channels are about. The actor which is being declared is no longer untyped, instead it lists requirements for those who wish to communicate with it: it will accept
JDouble messages as input (which is just
java.lang.Double for reasons discussed below), and it expects the sender to handle replies of type
Double. This is encoded as the pair preceding the
:+: operator, whose function is to build lists of these input/output type pairs, and like every good list it is terminated with a
TNil. The first type argument to the Channels trait describes the requirements for this actor when talking to its parent; in this case that is not done, hence the empty type list.
The implementation then concludes with the definition of the behavior for the
JDouble channel, which is to multiply it by itself and send it back to the sender. Note that the sender reference is passed into the behavior here, since it is typed such that sending anything but a
Double will not be accepted.
Having statically checked actor interactions is a worthy goal in itself, as has been brought up many times on the mailing list or when talking with fellow hAkkers at conferences, but having it enables more! Let us look at a slightly more complex example:
This actor follows the same structure, but it accepts a constructor argument describing how to build another actor. That recipe is used to create a child actor which is accessed by the targetRef. Besides initiating the asynchronous creation of the child the
createChild method also verifies that the StringToDouble actor is fit for being a good parent, i.e. that it can digest all messages the child may want to send to it. This example is a simple case since the
target actor does not send to its parent (see the first
target’s type signature).
The three lines before creating the child supply the contextual information needed to run the behavior below: when a String is received, it is parsed into a
java.lang.Double to be exact) and sent to the
targetRef using the “ask” operator. This does the roughly the same thing as your good old “ask pattern”, returning a
Future[Double] (since we have declared the target channel this way). This future is then mapped into a
Future[String] and piped back to the sender.
One revolutionary feature is that in this syntax the message flows consistently from left to right, a feat that is made possible without performance cost by implicit value classes.
The interesting part about this second example is that now we have a higher-order actor:
StringToDouble is comparable to a function which takes a function
JDouble => Double and turns it into a function
String => String. In the case at hand these functions are actually referentially transparent since the actors are stateless, making the connection to functional composition quite evident. In the general case this allows composition on a message protocol level, including aspects like timing of messages or altering delivery guarantees.
Viewing an actor as a function represented by its typed channel allows also another interpretation: the behavior to be invoked is determined not only by the recipient of the message but also the channel (since a channel list can contain multiple input–output pairs), which means that this implements dynamic multiple dispatch.
The approach shown above is merely the most static one (short of hard-wiring the child’s creation): there are other ways of achieving composition, for example by passing channel references between actors in messages. Since everything is statically type-checked, this opens up a whole new way of structuring large actor systems.
But enough of the theoretical talking. If you want to try out this collaboration in practice you could run an application like the following:
This shows the last element of the typed channels API: top-level actors are created by making use of the
ChannelExt Akka extension. The rest uses Future-trampolining to read lines from the terminal and feed them to the actor, displaying the results as we go.
If you want to read more about the details of the API, please refer to the Akka documentation. The full code for the samples—with all imports—is available here. And it works with the published artifacts of Akka 2.2-M1, so give it a spin!
The current state of the
akka-channels-experimental module is “experimental”, there are some features missing and the API will also still be changed (e.g. adding textual method names and possibly changing existing ones). One of the missing features which is visible in the sample is that it is currently not possible to declare input channels of primitive types like Double—due to an oversight this channel would not ever receive messages.
But that is unfortunately not the end of it: as this document explains Scala’s reflection library is currently not thread-safe (and 2.10.1 will also not be). I carefully worked around the issue by ensuring that not two actors are starting up at the same time (in case you were wondering about the lazy val up there).
These issues will be fixed with time, but it is never too early to look at a new tool and give feedback!