These slides are for my presentation at North-East Scala Symposium 2013 in Philadelphia, see http://nescala.org. The video is available at http://www.youtube.com/watch?v=PCk2SHufw3E
1. Typed Channels
and
Macros
Dr. Roland Kuhn
@rolandkuhn
söndag 10 februari 13
2. The Problem
someActor ! CommandOne
söndag 10 februari 13
3. The Problem
trait Command
case class CommandOne(param: String) extends Command
someActor ! CommandOne
söndag 10 februari 13
4. The Vision
someActor <-!- CommandOne(”msg”)
because the other does not compile
söndag 10 februari 13
5. But How?
• ActorRef must know about message types
– Actor type must be parameterized
• Message type is verified against that
söndag 10 februari 13
6. And the replies?
val f: Future[Response] =
someActor <-?- CommandOne(”hello”)
because the compiler knows
söndag 10 februari 13
7. And How This?
• ActorRef must know reply types
– Actor must be parameterized with them
• Reply types are extracted at call site
söndag 10 februari 13
8. No Type Pollution
• Generic Filter/Transform Actors
– accept management commands
– pass on generic other type
• Using just one type is not enough!
• Need to use type unions and allow multiple
possible reply types for one input
söndag 10 februari 13
9. Actors Do Compose
msg -?-> firstActor -?-> secondActor -!-> client
msg -?-> someService -*-> (_ map httpOk) -!-> client
Process wiring from the outside
söndag 10 februari 13
12. Relation to π-calculus
• Actors are composite processes
• Actor types are structural, not nominal
• wiring A͡B can be done situationally
http://doc.akka.io/ see Typed Channels
söndag 10 februari 13
13. That was the
Eloi world
Now we’re going to visit the Morlocks
söndag 10 februari 13
14. The Implementation
• Tagged type union with
:+:[(In, Out), ChannelList] <: ChannelList
• Value class ChannelRef[…](val a: ActorRef)
• Actor mixin Channels[…]
• WrappedMessage[…, LUB](val m: LUB)
• ops desugar to tell/ask after type check
söndag 10 februari 13
15. How to Declare it?
class OpinionatedEcho extends Actor
with Channels[TNil, (String, String) :+: TNil] {
channel[String] { (str, sender) ⇒ sender <-!- str }
// or
channel[String] {
case (”hello”, sender) ⇒ sender <-!- ”world”
case (x, sender) ⇒ sender <-!- s”dunno: $x”
}
}
“sender” will accept only String messages
söndag 10 februari 13
16. First Problem: Lambdas
• Type-checker transforms lambda before the
macro call
– pattern matches or PartialFunction literals
generate checks depending on static type info
• Behavior is not an argument to “channels”
• macro only emits object with right “apply”
söndag 10 februari 13
17. First Problem: Lambdas
private class Behaviorist[-R, Ch: ru.TypeTag](
wrapped: Boolean) extends (R ⇒ Unit) {
// ...
def apply(recv: R): Unit = {
val tt = ru.typeTag[Ch]
behavior ++= (
for (t ← inputChannels(ru)(tt.tpe))
yield
tt.mirror.runtimeClass(t.widen) -> ff(recv))
}
}
calling channels[_] registers the behavior
söndag 10 februari 13
18. The Gory Details
def impl[LUB, ReplyChannels <: ChannelList,
MsgTChan <: ChannelList,
MsgT: c.WeakTypeTag,
MyCh <: ChannelList: c.WeakTypeTag,
ParentCh <: ChannelList: c.WeakTypeTag](
c: Context {
type PrefixType = Channels[ParentCh, MyCh]
}): c.Expr[(Nothing ⇒ Unit)] = {
// some type calculations happen here
val prepTree = reify(...)
reify {
prepTree.splice
c.prefix.splice.behaviorist[
(MsgT, ChannelRef[ReplyChannels]) ⇒ Unit, MsgT](
bool(c, false).splice)(universe.typeTag[MsgT])
}
}
söndag 10 februari 13
19. The Gory Details
trait Channels[P <: ChannelList, C <: ChannelList] {
this: Actor ⇒
def channel[T]: (Nothing ⇒ Unit) =
macro macros.Channel.impl[Any, ChannelList,
ChannelList, T, C, P]
def behaviorist[R, Ch: ru.TypeTag](wrapped: Boolean)
: (R ⇒ Unit) = new Behaviorist[R, Ch](wrapped)
// ...
}
söndag 10 februari 13
20. Sample Type Calculation
final def missingChannels(u: Universe)(
channels: u.Type, required: List[u.Type]): List[u.Type] = {
import u._
// making the top-level method recursive blows up the compiler
def rec(ch: Type, req: List[Type]): List[Type] = {
ch match {
case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒
rec(tail, req filterNot (_ <:< in))
case last ⇒ req filterNot (_ <:< last)
}
}
rec(channels, required)
}
söndag 10 februari 13
21. How to Return a Type
def impl[..., ReplyChannels <: ChannelList, ...]
(c: Context): c.Expr[(Nothing ⇒ Unit)] = {
// calculate “channels: u.Type” and then:
implicit val ttReplyChannels =
c.TypeTag[ReplyChannels](channels)
reify {
...[(..., ...[ReplyChannels]) ⇒ ..., ...]...
}
}
“reify” picks up TypeTags and uses their .tpe
söndag 10 februari 13
22. Sharing Code with Runtime
final def x(u: Universe)(list: u.Type, msg: u.Type)
: List[u.Type] = {
val imp = u.mkImporter(ru)
val tpeReplyChannels =
imp.importType(ru.typeOf[ReplyChannels[_]])
val tpeTNil = imp.importType(ru.typeOf[TNil])
// ...
}
otherwise there will be weird exceptions
söndag 10 februari 13
23. How to test it?
def mkToolbox(compileOptions: String = "")
: ToolBox[_ <: scala.reflect.api.Universe] = {
val m = scala.reflect.runtime.currentMirror
m.mkToolBox(options = compileOptions)
}
def eval(code: String, compileOptions: String =
"-cp akka-actor/target/classes:akka-channels/target/classes")
: Any = {
val tb = mkToolbox(compileOptions)
tb.eval(tb.parse(code))
}
söndag 10 februari 13
24. How to test it?
intercept[ToolBoxError] {
eval("""
import akka.channels._
import ChannelSpec._
implicit val c = new ChannelRef[TNil](null)
new ChannelRef[(A, C) :+: TNil](null) <-!- B
""")
}.message must include(
"target ChannelRef does not support messages of " +
"types akka.channels.ChannelSpec.B.type")
söndag 10 februari 13
25. get it and learn more
http://akka.io
http://letitcrash.com
http://typesafe.com
söndag 10 februari 13