From db40d51c05b05a91844b1bcfb47fe169290454cf Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 31 Dec 2012 16:37:58 +0100 Subject: [PATCH 01/26] half-done PoC of typed channels --- .../src/main/scala/akka/makkros/Test.scala | 21 +++ .../scala/akka/channels/ChannelSpec.scala | 156 ++++++++++++++++++ .../akka/channels/ChannelExtension.scala | 20 +++ .../main/scala/akka/channels/ChannelRef.scala | 55 ++++++ .../main/scala/akka/channels/Channels.scala | 142 ++++++++++++++++ .../main/scala/akka/channels/package.scala | 18 ++ project/AkkaBuild.scala | 21 ++- 7 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 akka-macro-tests/src/main/scala/akka/makkros/Test.scala create mode 100644 akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala create mode 100644 akka-macros/src/main/scala/akka/channels/ChannelExtension.scala create mode 100644 akka-macros/src/main/scala/akka/channels/ChannelRef.scala create mode 100644 akka-macros/src/main/scala/akka/channels/Channels.scala create mode 100644 akka-macros/src/main/scala/akka/channels/package.scala diff --git a/akka-macro-tests/src/main/scala/akka/makkros/Test.scala b/akka-macro-tests/src/main/scala/akka/makkros/Test.scala new file mode 100644 index 0000000000..cd30013027 --- /dev/null +++ b/akka-macro-tests/src/main/scala/akka/makkros/Test.scala @@ -0,0 +1,21 @@ +package akka.makkros + +import language.experimental.macros +import scala.reflect.macros.Context +import scala.tools.reflect.ToolBox +import scala.reflect.ClassTag +import scala.tools.reflect.ToolBoxError + +object Test { + + def eval(code: String, compileOptions: String = "-cp akka-macros/target/classes"): Any = { + val tb = mkToolbox(compileOptions) + tb.eval(tb.parse(code)) + } + + def mkToolbox(compileOptions: String = ""): ToolBox[_ <: scala.reflect.api.Universe] = { + val m = scala.reflect.runtime.currentMirror + m.mkToolBox(options = compileOptions) + } + +} \ No newline at end of file diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala new file mode 100644 index 0000000000..056ee6ec34 --- /dev/null +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -0,0 +1,156 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.channels + +import akka.testkit.AkkaSpec +import akka.testkit.ImplicitSender +import akka.actor.ActorRef +import akka.makkros.Test._ +import scala.tools.reflect.ToolBoxError + +object ChannelSpec { + + trait Msg + + trait A extends Msg + object A extends A + object A1 extends A + object A2 extends A + + trait B extends Msg + object B extends B + + trait C extends Msg + object C extends C + + trait D extends Msg + object D extends D + + // used for sender verification in the first two test cases + class Tester extends Channels[Parent[A :-: B :-: TNil], Channel[A, C] :=: Channel[B, D] :=: TNil] { + channel[A.type] { + case (A, s) ⇒ s ! C + } + channel[B] { + case (B, s) ⇒ s ! D + } + } + class RecvC(ref: ActorRef) extends Channels[Parent[TNil], Channel[C, Nothing] :=: TNil] { + channel[C] { case (x, _) ⇒ ref ! x } + } + + // pos compile test for multiple reply channels + class SubChannels extends Channels[Parent[TNil], Channel[A, B] :=: Channel[A, C] :=: TNil] { + channel[A] { + case (A1, x) ⇒ + x ! B + x ! C + } + } +} + +class ChannelSpec extends AkkaSpec with ImplicitSender { + + import ChannelSpec._ + + "Channels" must { + + "construct refs" in { + val ref = ChannelExt(system).actorOf(new Tester) + ref ! A + expectMsg(C) + lastSender must be(ref.actorRef) + ref ! B + expectMsg(D) + lastSender must be(ref.actorRef) + } + + "select return channels" in { + val ref = ChannelExt(system).actorOf(new Tester) + implicit val sender = ChannelExt(system).actorOf(new RecvC(testActor)) + ref ! A + expectMsg(C) + lastSender must be(sender.actorRef) + } + + "not permit wrong message type" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new ChannelRef[Channel[A, C] :=: TNil](null) ! B + """.stripMargin) + }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.B.type") + } + + "not permit wrong message type in complex channel" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new ChannelRef[Channel[A, C] :=: Channel[B, D] :=: TNil](null) ! C + """.stripMargin) + }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.C.type") + } + + "not permit unfit sender ref" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[Channel[C, D] :=: TNil](null) + |new ChannelRef[Channel[A, B] :=: TNil](null) ! A + """.stripMargin) + }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B") + } + + "permit any sender for Nothing replies" in { + implicit val s = new ChannelRef[TNil](testActor) + new ChannelRef[Channel[A, Nothing]:=: TNil](testActor) ! A + expectMsg(A) + } + + "require complete reply type sets" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[TNil](null) + |new ChannelRef[Channel[A, B] :=: Channel[A, C] :=: TNil](null) ! A + """.stripMargin) + }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B, akka.channels.ChannelSpec.C") + } + + "not permit nonsensical channel declarations" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new Channels[Parent[TNil], Channel[A, B] :=: TNil] { + | channel[B] { + | case (B, _) => + | } + |} + """.stripMargin) + }.message must include("no channel defined for type akka.channels.ChannelSpec.B") + } + + "not permit subchannel replies" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new Channels[Parent[TNil], Channel[A, B] :=: Channel[A1.type, C] :=: TNil] { + | channel[A] { + | case (A1, x) => x ! C + | } + |} + """.stripMargin) + }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.C.type") + } + + } + +} \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala b/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala new file mode 100644 index 0000000000..bd18f43e0c --- /dev/null +++ b/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.channels + +import akka.actor.ExtensionKey +import akka.actor.Extension +import akka.actor.ExtendedActorSystem +import scala.reflect.runtime.universe._ +import akka.actor.Props +import scala.reflect.ClassTag +import scala.reflect.runtime.universe + +object ChannelExt extends ExtensionKey[ChannelExtension] + +class ChannelExtension(system: ExtendedActorSystem) extends Extension { + def actorOf[Ch <: ChannelList: TypeTag](factory: ⇒ Channels[_, Ch]): ChannelRef[Ch] = + new ChannelRef[Ch](system.actorOf(Props(factory))) +} diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala new file mode 100644 index 0000000000..865effec11 --- /dev/null +++ b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.channels + +import language.experimental.macros +import akka.actor.ActorRef +import scala.reflect.runtime.universe.TypeTag +import scala.reflect.macros.Context +import scala.annotation.tailrec +import scala.reflect.macros.Universe +import akka.actor.Actor + +class ChannelRef[+T <: ChannelList: TypeTag](val actorRef: ActorRef) { + + def ![M](msg: M): Unit = macro ChannelRef.tell[T, M] + +} + +object ChannelRef { + import Channels._ + + def tell[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[T] + })(msg: c.Expr[M]): c.Expr[Unit] = { + val out = replyChannels(c.universe)(c.weakTypeOf[T], c.weakTypeOf[M]) + if (out.isEmpty) { + c.error(c.enclosingPosition, s"This ChannelRef does not support messages of type ${c.weakTypeOf[M]}") + return c.universe.reify(()) + } + val replyChannel = c.inferImplicitValue(c.typeOf[ChannelRef[_]]) + if (!replyChannel.isEmpty) { + import c.universe._ + val list = replyChannel.tpe match { + case TypeRef(_, _, param :: Nil) ⇒ param + } + val m = missingChannels(c.universe)(list, out) filterNot (_ =:= weakTypeOf[Nothing]) + if (m.isEmpty) { + val sender = c.Expr[ChannelRef[_]](replyChannel)(c.WeakTypeTag(replyChannel.tpe)) + c.universe.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice.actorRef)) + } else { + c.error(c.enclosingPosition, s"The implicit sender `${replyChannel.symbol}` does not support messages of the reply types ${m.mkString(", ")}") + c.universe.reify(()) + } + } else { + val senderTree = c.inferImplicitValue(c.typeOf[ActorRef]) + val sender = + if (senderTree.isEmpty) c.universe.reify(Actor.noSender) + else c.Expr(senderTree)(c.WeakTypeTag(senderTree.tpe)) + c.universe.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice)) + } + } + +} \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala new file mode 100644 index 0000000000..4f9b037a0a --- /dev/null +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.channels + +import language.experimental.macros +import akka.actor.Actor +import scala.reflect.macros.Context +import scala.reflect.runtime.universe.TypeTag +import scala.reflect.macros.Universe +import scala.runtime.AbstractPartialFunction + +trait Channels[P <: Parent[_], C <: ChannelList] extends Actor { + + import Channels._ + + /* + * Warning Ugly Hack Ahead + * + * The current problem is that the partial function literals shall be + * checked against the right types, but that leads to unfortunate + * optimizations in the pattern matcher (it leaves out instanceof checks + * based on the static types). Hence the try-catch in receive’s + * applyOrElse implementation. What I’d really like is a way to re-create + * the PF literals after type-checking but with a different expected type. + */ + private var behavior = List.empty[Recv[Any, ChannelList]] + + def channel[T]: Channels[P, C]#Behaviorist[T, _ <: ChannelList] = macro Channels.channelImpl[T, C, P, ChannelList] + + protected def _channel[T, Ch <: ChannelList] = new Behaviorist[T, Ch] + protected class Behaviorist[T, Ch <: ChannelList] { + def apply(recv: Recv[T, Ch]): Unit = behavior ::= recv.asInstanceOf[Recv[Any, ChannelList]] + } + + final lazy val receive = new AbstractPartialFunction[Any, Unit] { + + val behaviors = behavior.reverse + + override def applyOrElse[A, B >: Unit](x: A, default: A ⇒ B): B = { + val envelope = (x, new ChannelRef(sender)) + def rec(list: List[Recv[Any, ChannelList]]): B = list match { + case head :: tail ⇒ + try head.applyOrElse(envelope, (dummy: (A, ChannelRef[_])) ⇒ rec(tail)) + catch { + // see comment above for why this ugliness + case _: ClassCastException ⇒ rec(tail) + } + case _ ⇒ default(x) + } + rec(behaviors) + } + + def isDefinedAt(x: Any): Boolean = { + val envelope = (x, null) // hmm ... + behaviors.exists(_.isDefinedAt(envelope)) + } + } +} + +object Channels { + + type Recv[T, Ch <: ChannelList] = PartialFunction[(T, ChannelRef[Ch]), Unit] + + /** + * This macro transforms a channel[] call which returns “some” Behaviorist + * into a _channel[] call with precise reply channel descriptors, so that the + * partial function it is applied to can enjoy proper type checking. + */ + def channelImpl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: Parent[_]: c.WeakTypeTag, Ch <: ChannelList]( + c: Context { + type PrefixType = Channels[P, C] + }): c.Expr[Channels[P, C]#Behaviorist[T, Ch]] = { + + import c.universe._ + val out = replyChannels(c.universe)(c.weakTypeOf[C], c.weakTypeOf[T]) + if (out.isEmpty) { + c.error(c.enclosingPosition, s"no channel defined for type ${c.weakTypeOf[T]}") + reify(null) + } else { + val channels = toChannels(c.universe)(out) + c.Expr(TypeApply( + Select(c.prefix.tree, newTermName("_channel")), List( + TypeTree().setType(c.weakTypeOf[T]), + TypeTree().setType(channels)))) + } + } + + /** + * find all input channels matching the given message type and return a + * list of their respective reply channels + */ + final def replyChannels(u: Universe)(list: u.Type, msg: u.Type): List[u.Type] = { + import u._ + def rec(l: Type, acc: List[Type]): List[Type] = { + l match { + case TypeRef(_, _, TypeRef(_, _, in :: out :: Nil) :: tail :: Nil) if msg <:< in ⇒ + rec(tail, out :: acc) + case TypeRef(_, _, _ :: tail :: Nil) ⇒ + rec(tail, acc) + case _ ⇒ acc.reverse + } + } + rec(list, Nil) + } + + /** + * filter from the `required` list of types all which are subtypes of inputs of the ChannelList + */ + 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 (when compiling the macro itself) + def rec(ch: Type, req: List[Type]): List[Type] = { + ch match { + case TypeRef(_, _, TypeRef(_, _, in :: _) :: (tail: Type) :: Nil) ⇒ rec(tail, req filterNot (_ <:< in)) + case _ ⇒ req + } + } + rec(channels, required) + } + + /** + * convert a list of types List(, , ...) into a ChannelList + * ( Channel[, Nothing] :=: Channel[, Nothing] :=: ... :=: TNil ) + */ + final def toChannels(u: Universe)(list: List[u.Type]): u.Type = { + import u._ + def rec(l: List[Type], acc: Type): Type = l match { + case head :: (tail: List[Type]) ⇒ + rec(tail, + appliedType(weakTypeOf[:=:[_, _]].typeConstructor, List( + appliedType(weakTypeOf[Channel[_, _]].typeConstructor, List( + head, + weakTypeOf[Nothing])), + acc))) + case _ ⇒ acc + } + rec(list.reverse, weakTypeOf[TNil]) + } + +} \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/package.scala b/akka-macros/src/main/scala/akka/channels/package.scala new file mode 100644 index 0000000000..c1499b5586 --- /dev/null +++ b/akka-macros/src/main/scala/akka/channels/package.scala @@ -0,0 +1,18 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka + +package object channels { + trait Channel[I, O] + + sealed trait ChannelList + sealed trait ParentList + sealed trait TNil extends ChannelList with ParentList + + sealed trait :=:[A <: Channel[_, _], B <: ChannelList] extends ChannelList + sealed trait :-:[A, B <: ParentList] extends ParentList + + trait Parent[T <: ParentList] +} \ No newline at end of file diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 6bca2ff7c0..b5d81b16e3 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -73,7 +73,7 @@ object AkkaBuild extends Build { generatedPdf in Sphinx <<= generatedPdf in Sphinx in LocalProject(docs.id) map identity ), - aggregate = Seq(actor, testkit, actorTests, dataflow, remote, remoteTests, camel, cluster, slf4j, agent, transactor, mailboxes, zeroMQ, kernel, akkaSbtPlugin, osgi, osgiAries, docs, contrib, samples) + aggregate = Seq(actor, testkit, actorTests, dataflow, remote, remoteTests, camel, cluster, slf4j, agent, transactor, mailboxes, zeroMQ, kernel, akkaSbtPlugin, osgi, osgiAries, docs, contrib, samples, macros, macroTests) ) lazy val actor = Project( @@ -409,6 +409,24 @@ object AkkaBuild extends Build { ) ) configs (MultiJvm) + lazy val macros = Project( + id = "akka-macros", + base = file("akka-macros"), + dependencies = Seq(actor), + settings = defaultSettings ++ Seq( + libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-reflect" % _) + ) + ) + + lazy val macroTests = Project( + id = "akka-macro-tests", + base = file("akka-macro-tests"), + dependencies = Seq(macros, testkit % "compile;test->test"), + settings = defaultSettings ++ Seq( + libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-compiler" % _) + ) + ) + // Settings override lazy val settings = @@ -701,7 +719,6 @@ object Dependencies { val ariesBlueprint = "org.apache.aries.blueprint" % "org.apache.aries.blueprint" % "0.3.2" // ApacheV2 val osgiCore = "org.osgi" % "org.osgi.core" % "4.2.0" // ApacheV2 - // Camel Sample val camelJetty = "org.apache.camel" % "camel-jetty" % camelCore.revision // ApacheV2 From 8a4fca72f786ffd0d389bc5f0af59063b9b03ff4 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 31 Dec 2012 18:38:06 +0100 Subject: [PATCH 02/26] add selfChannel and createChildren --- .../scala/akka/channels/ChannelSpec.scala | 54 +++++++++++++++++-- .../main/scala/akka/channels/Channels.scala | 45 +++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 056ee6ec34..7263ebc3c9 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -13,7 +13,7 @@ import scala.tools.reflect.ToolBoxError object ChannelSpec { trait Msg - + trait A extends Msg object A extends A object A1 extends A @@ -21,10 +21,10 @@ object ChannelSpec { trait B extends Msg object B extends B - + trait C extends Msg object C extends C - + trait D extends Msg object D extends D @@ -49,6 +49,24 @@ object ChannelSpec { x ! C } } + + // pos compile test for children + class Children extends Channels[Parent[TNil], Channel[A, B] :=: Channel[C, D] :=: TNil] { + val c = createChild(new Channels[Parent[A :-: TNil], Channel[B, C]:=: TNil] { + channel[B] { case (B, s) ⇒ s ! C } + }) + + var client: ActorRef = _ + channel[A] { + case (A, s) ⇒ c ! B; client = sender + } + channel[C] { + case (C, _) ⇒ client ! C + } + + createChild(new Channels[Parent[C :-: TNil], TNil]) + createChild(new Channels[Parent[A :-: C :-: TNil], TNil]) + } } class ChannelSpec extends AkkaSpec with ImplicitSender { @@ -151,6 +169,36 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.C.type") } + "not permit Nothing children" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new Channels[Parent[TNil], Channel[A, B] :=: Channel[C, D] :=: TNil] { + | createChild(new Channels) + |} + """.stripMargin) + }.message must include("Parent argument must not be Nothing") + } + + "not permit too demanding children" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new Channels[Parent[TNil], Channel[A, B] :=: Channel[C, D] :=: TNil] { + | createChild(new Channels[Parent[B :-: TNil], TNil]) + |} + """.stripMargin) + }.message must include("This actor cannot support a child requiring channels akka.channels.ChannelSpec.B") + } + + "have a working selfChannel" in { + val ref = ChannelExt(system).actorOf(new Children) + ref ! A + expectMsg(C) + } + } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index 4f9b037a0a..19e0bac74b 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -10,11 +10,16 @@ import scala.reflect.macros.Context import scala.reflect.runtime.universe.TypeTag import scala.reflect.macros.Universe import scala.runtime.AbstractPartialFunction +import akka.actor.Props -trait Channels[P <: Parent[_], C <: ChannelList] extends Actor { +class Channels[P <: Parent[_], C <: ChannelList: TypeTag] extends Actor { import Channels._ + def createChild[Pa <: Parent[_], Ch <: ChannelList](factory: Channels[Pa, Ch]): ChannelRef[Ch] = macro createChildImpl[C, Pa, Ch] + + implicit val selfChannel = new ChannelRef[C](self) + /* * Warning Ugly Hack Ahead * @@ -27,7 +32,7 @@ trait Channels[P <: Parent[_], C <: ChannelList] extends Actor { */ private var behavior = List.empty[Recv[Any, ChannelList]] - def channel[T]: Channels[P, C]#Behaviorist[T, _ <: ChannelList] = macro Channels.channelImpl[T, C, P, ChannelList] + def channel[T]: Channels[P, C]#Behaviorist[T, _ <: ChannelList] = macro channelImpl[T, C, P, ChannelList] protected def _channel[T, Ch <: ChannelList] = new Behaviorist[T, Ch] protected class Behaviorist[T, Ch <: ChannelList] { @@ -87,6 +92,42 @@ object Channels { } } + def createChildImpl[C <: ChannelList: c.WeakTypeTag, Pa <: Parent[_]: c.WeakTypeTag, Ch <: ChannelList: c.WeakTypeTag]( + c: Context { + type PrefixType = Channels[_, C] + })(factory: c.Expr[Channels[Pa, Ch]]): c.Expr[ChannelRef[Ch]] = { + + import c.universe._ + if (weakTypeOf[Pa] =:= weakTypeOf[Nothing]) { + c.abort(c.enclosingPosition, "Parent argument must not be Nothing") + } + if (weakTypeOf[Ch] =:= weakTypeOf[Nothing]) { + c.abort(c.enclosingPosition, "channel list must not be Nothing") + } + val missing = missingChannels(c.universe)(weakTypeOf[C], parentChannels(c.universe)(weakTypeOf[Pa])) + if (missing.isEmpty) { + implicit val t = c.TypeTag[Ch](c.weakTypeOf[Ch]) + reify(new ChannelRef[Ch](c.prefix.splice.context.actorOf(Props(factory.splice)))) + } else { + c.error(c.enclosingPosition, s"This actor cannot support a child requiring channels ${missing mkString ", "}") + reify(null) + } + } + + /** + * get all required channels from a Parent[_] + */ + final def parentChannels(u: Universe)(list: u.Type): List[u.Type] = { + import u._ + def rec(l: u.Type, acc: List[u.Type]): List[u.Type] = l match { + case TypeRef(_, _, ch :: tail :: Nil) ⇒ rec(tail, ch :: acc) + case _ ⇒ acc.reverse + } + list match { + case TypeRef(_, _, ch :: Nil) ⇒ rec(ch, Nil) + } + } + /** * find all input channels matching the given message type and return a * list of their respective reply channels From 3521e27efe76fa2879aca7ebe6f82375d5395ec4 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 1 Jan 2013 21:30:59 +0100 Subject: [PATCH 03/26] clean it up a little - remove Parent trait and use ChannelList to describe the parent - remove ugly hack and switch to erasure-based dispatch to the channels - add parentChannel - make ChannelRef a value class --- .../src/main/scala/akka/makkros/Test.scala | 2 +- .../scala/akka/channels/ChannelSpec.scala | 24 +-- .../main/scala/akka/channels/ChannelRef.scala | 2 +- .../main/scala/akka/channels/Channels.scala | 140 ++++++++++++------ .../main/scala/akka/channels/package.scala | 7 +- 5 files changed, 110 insertions(+), 65 deletions(-) diff --git a/akka-macro-tests/src/main/scala/akka/makkros/Test.scala b/akka-macro-tests/src/main/scala/akka/makkros/Test.scala index cd30013027..efb016175f 100644 --- a/akka-macro-tests/src/main/scala/akka/makkros/Test.scala +++ b/akka-macro-tests/src/main/scala/akka/makkros/Test.scala @@ -8,7 +8,7 @@ import scala.tools.reflect.ToolBoxError object Test { - def eval(code: String, compileOptions: String = "-cp akka-macros/target/classes"): Any = { + def eval(code: String, compileOptions: String = "-cp akka-actor/target/classes:akka-macros/target/classes"): Any = { val tb = mkToolbox(compileOptions) tb.eval(tb.parse(code)) } diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 7263ebc3c9..7ceaa7c306 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -29,7 +29,7 @@ object ChannelSpec { object D extends D // used for sender verification in the first two test cases - class Tester extends Channels[Parent[A :-: B :-: TNil], Channel[A, C] :=: Channel[B, D] :=: TNil] { + class Tester extends Channels[TNil, Channel[A, C] :=: Channel[B, D] :=: TNil] { channel[A.type] { case (A, s) ⇒ s ! C } @@ -37,12 +37,12 @@ object ChannelSpec { case (B, s) ⇒ s ! D } } - class RecvC(ref: ActorRef) extends Channels[Parent[TNil], Channel[C, Nothing] :=: TNil] { + class RecvC(ref: ActorRef) extends Channels[TNil, Channel[C, Nothing] :=: TNil] { channel[C] { case (x, _) ⇒ ref ! x } } // pos compile test for multiple reply channels - class SubChannels extends Channels[Parent[TNil], Channel[A, B] :=: Channel[A, C] :=: TNil] { + class SubChannels extends Channels[TNil, Channel[A, B] :=: Channel[A, C] :=: TNil] { channel[A] { case (A1, x) ⇒ x ! B @@ -51,8 +51,8 @@ object ChannelSpec { } // pos compile test for children - class Children extends Channels[Parent[TNil], Channel[A, B] :=: Channel[C, D] :=: TNil] { - val c = createChild(new Channels[Parent[A :-: TNil], Channel[B, C]:=: TNil] { + class Children extends Channels[TNil, Channel[A, B] :=: Channel[C, D] :=: TNil] { + val c = createChild(new Channels[Channel[A, Nothing]:=: TNil, Channel[B, C]:=: TNil] { channel[B] { case (B, s) ⇒ s ! C } }) @@ -64,8 +64,8 @@ object ChannelSpec { case (C, _) ⇒ client ! C } - createChild(new Channels[Parent[C :-: TNil], TNil]) - createChild(new Channels[Parent[A :-: C :-: TNil], TNil]) + createChild(new Channels[Channel[C, Nothing]:=: TNil, TNil]) + createChild(new Channels[Channel[A, Nothing]:=: Channel[C, Nothing]:=: TNil, TNil]) } } @@ -146,7 +146,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[Parent[TNil], Channel[A, B] :=: TNil] { + |new Channels[TNil, Channel[A, B] :=: TNil] { | channel[B] { | case (B, _) => | } @@ -160,7 +160,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[Parent[TNil], Channel[A, B] :=: Channel[A1.type, C] :=: TNil] { + |new Channels[TNil, Channel[A, B] :=: Channel[A1.type, C] :=: TNil] { | channel[A] { | case (A1, x) => x ! C | } @@ -174,7 +174,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[Parent[TNil], Channel[A, B] :=: Channel[C, D] :=: TNil] { + |new Channels[TNil, Channel[A, B] :=: Channel[C, D] :=: TNil] { | createChild(new Channels) |} """.stripMargin) @@ -186,8 +186,8 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[Parent[TNil], Channel[A, B] :=: Channel[C, D] :=: TNil] { - | createChild(new Channels[Parent[B :-: TNil], TNil]) + |new Channels[TNil, Channel[A, B] :=: Channel[C, D] :=: TNil] { + | createChild(new Channels[Channel[B, Nothing] :=: TNil, TNil]) |} """.stripMargin) }.message must include("This actor cannot support a child requiring channels akka.channels.ChannelSpec.B") diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala index 865effec11..3a6ff2ad1d 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala @@ -12,7 +12,7 @@ import scala.annotation.tailrec import scala.reflect.macros.Universe import akka.actor.Actor -class ChannelRef[+T <: ChannelList: TypeTag](val actorRef: ActorRef) { +class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { def ![M](msg: M): Unit = macro ChannelRef.tell[T, M] diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index 19e0bac74b..41bac0fb63 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -11,55 +11,101 @@ import scala.reflect.runtime.universe.TypeTag import scala.reflect.macros.Universe import scala.runtime.AbstractPartialFunction import akka.actor.Props +import scala.collection.immutable +import scala.collection.mutable.ArrayBuffer +import scala.reflect.{ classTag, ClassTag } -class Channels[P <: Parent[_], C <: ChannelList: TypeTag] extends Actor { +/** + * Typed channels atop untyped actors. + * + * The idea is that the actor declares all its input types up front, including + * what it expects the sender to handle wrt.replies, and then ChannelRef + * carries this information for statically verifying that messages sent to an + * actor have an actual chance of being processed. + * + * There are several implementation-imposed restrictions: + * + * - not two channels with different input types may have the same erased + * type; this is currently not enforced at compile time (and leads to + * channels being “ignored” at runtime) + * - messages received by the actor are dispatched to channels based on the + * erased type, which may be less precise than the actual channel type; this + * can lead to ClassCastExceptions if sending through the untyped ActorRef + */ +class Channels[P <: ChannelList, C <: ChannelList: TypeTag] extends Actor { import Channels._ - def createChild[Pa <: Parent[_], Ch <: ChannelList](factory: Channels[Pa, Ch]): ChannelRef[Ch] = macro createChildImpl[C, Pa, Ch] - - implicit val selfChannel = new ChannelRef[C](self) - - /* - * Warning Ugly Hack Ahead - * - * The current problem is that the partial function literals shall be - * checked against the right types, but that leads to unfortunate - * optimizations in the pattern matcher (it leaves out instanceof checks - * based on the static types). Hence the try-catch in receive’s - * applyOrElse implementation. What I’d really like is a way to re-create - * the PF literals after type-checking but with a different expected type. + /** + * Create a child actor with properly typed ChannelRef, verifying that this + * actor can handle everything which the child tries to send via its + * `parent` ChannelRef. */ - private var behavior = List.empty[Recv[Any, ChannelList]] + def createChild[Pa <: ChannelList, Ch <: ChannelList](factory: Channels[Pa, Ch]): ChannelRef[Ch] = macro createChildImpl[C, Pa, Ch] + /** + * Properly typed ChannelRef for the context.parent. + */ + def parentChannel: ChannelRef[P] = new ChannelRef(context.parent) + + /** + * The properly typed self-channel is used implicitly when sending to other + * typed channels for verifying that replies can be handled. + */ + implicit def selfChannel = new ChannelRef[C](self) + + private var behavior = Map.empty[Class[_], Recv[Any, ChannelList]] + + /** + * Declare an input channel of the given type; the returned object takes a partial function: + * + * {{{ + * channel[A] { + * case (a, s) => + * // a is of type A and + * // s is a ChannelRef for the sender, capable of sending the declared reply type for A + * } + * }}} + */ def channel[T]: Channels[P, C]#Behaviorist[T, _ <: ChannelList] = macro channelImpl[T, C, P, ChannelList] - protected def _channel[T, Ch <: ChannelList] = new Behaviorist[T, Ch] - protected class Behaviorist[T, Ch <: ChannelList] { - def apply(recv: Recv[T, Ch]): Unit = behavior ::= recv.asInstanceOf[Recv[Any, ChannelList]] + protected def _channel[T, Ch <: ChannelList](cls: Class[_]) = new Behaviorist[T, Ch](cls) + protected class Behaviorist[T, Ch <: ChannelList](cls: Class[_]) { + def apply(recv: Recv[T, Ch]): Unit = + behavior += cls -> recv.asInstanceOf[Recv[Any, ChannelList]] } + /** + * Sort so that subtypes always precede their supertypes, but without + * obeying any order between unrelated subtypes (insert sort). + */ + private def sortClasses(in: Iterable[Class[_]]): immutable.Seq[Class[_]] = + (new ArrayBuffer[Class[_]](in.size) /: in) { (buf, cls) ⇒ + buf.indexWhere(_ isAssignableFrom cls) match { + case -1 ⇒ buf append cls + case x ⇒ buf insert (x, cls) + } + buf + }.to[immutable.IndexedSeq] + final lazy val receive = new AbstractPartialFunction[Any, Unit] { - val behaviors = behavior.reverse + val index = sortClasses(behavior.keys) override def applyOrElse[A, B >: Unit](x: A, default: A ⇒ B): B = { - val envelope = (x, new ChannelRef(sender)) - def rec(list: List[Recv[Any, ChannelList]]): B = list match { - case head :: tail ⇒ - try head.applyOrElse(envelope, (dummy: (A, ChannelRef[_])) ⇒ rec(tail)) - catch { - // see comment above for why this ugliness - case _: ClassCastException ⇒ rec(tail) - } - case _ ⇒ default(x) + val msgClass = x.getClass + index find (_ isAssignableFrom msgClass) match { + case None ⇒ default(x) + case Some(cls) ⇒ behavior(cls).applyOrElse((x, new ChannelRef(sender)), (pair: (A, ChannelRef[C])) ⇒ default(pair._1)) } - rec(behaviors) } def isDefinedAt(x: Any): Boolean = { - val envelope = (x, null) // hmm ... - behaviors.exists(_.isDefinedAt(envelope)) + val msgClass = x.getClass + index find (_ isAssignableFrom msgClass) match { + case None ⇒ false + case Some(cls) ⇒ behavior(cls).isDefinedAt((x, new ChannelRef(sender))) + } } } } @@ -73,7 +119,7 @@ object Channels { * into a _channel[] call with precise reply channel descriptors, so that the * partial function it is applied to can enjoy proper type checking. */ - def channelImpl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: Parent[_]: c.WeakTypeTag, Ch <: ChannelList]( + def channelImpl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: ChannelList: c.WeakTypeTag, Ch <: ChannelList]( c: Context { type PrefixType = Channels[P, C] }): c.Expr[Channels[P, C]#Behaviorist[T, Ch]] = { @@ -85,14 +131,20 @@ object Channels { reify(null) } else { val channels = toChannels(c.universe)(out) - c.Expr(TypeApply( - Select(c.prefix.tree, newTermName("_channel")), List( - TypeTree().setType(c.weakTypeOf[T]), - TypeTree().setType(channels)))) + c.Expr(Apply( + TypeApply( + Select(c.prefix.tree, "_channel"), List( + TypeTree().setType(c.weakTypeOf[T]), + TypeTree().setType(channels))), + List(Select( + TypeApply( + Select(Select(Ident("scala"), "reflect"), "classTag"), + List(TypeTree().setType(c.weakTypeOf[T]))), + "runtimeClass")))) } } - def createChildImpl[C <: ChannelList: c.WeakTypeTag, Pa <: Parent[_]: c.WeakTypeTag, Ch <: ChannelList: c.WeakTypeTag]( + def createChildImpl[C <: ChannelList: c.WeakTypeTag, Pa <: ChannelList: c.WeakTypeTag, Ch <: ChannelList: c.WeakTypeTag]( c: Context { type PrefixType = Channels[_, C] })(factory: c.Expr[Channels[Pa, Ch]]): c.Expr[ChannelRef[Ch]] = { @@ -104,28 +156,26 @@ object Channels { if (weakTypeOf[Ch] =:= weakTypeOf[Nothing]) { c.abort(c.enclosingPosition, "channel list must not be Nothing") } - val missing = missingChannels(c.universe)(weakTypeOf[C], parentChannels(c.universe)(weakTypeOf[Pa])) + val missing = missingChannels(c.universe)(weakTypeOf[C], inputChannels(c.universe)(weakTypeOf[Pa])) if (missing.isEmpty) { implicit val t = c.TypeTag[Ch](c.weakTypeOf[Ch]) reify(new ChannelRef[Ch](c.prefix.splice.context.actorOf(Props(factory.splice)))) } else { c.error(c.enclosingPosition, s"This actor cannot support a child requiring channels ${missing mkString ", "}") - reify(null) + reify(???) } } /** * get all required channels from a Parent[_] */ - final def parentChannels(u: Universe)(list: u.Type): List[u.Type] = { + final def inputChannels(u: Universe)(list: u.Type): List[u.Type] = { import u._ def rec(l: u.Type, acc: List[u.Type]): List[u.Type] = l match { - case TypeRef(_, _, ch :: tail :: Nil) ⇒ rec(tail, ch :: acc) - case _ ⇒ acc.reverse - } - list match { - case TypeRef(_, _, ch :: Nil) ⇒ rec(ch, Nil) + case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, in :: acc) + case _ ⇒ acc.reverse } + rec(list, Nil) } /** diff --git a/akka-macros/src/main/scala/akka/channels/package.scala b/akka-macros/src/main/scala/akka/channels/package.scala index c1499b5586..a9734975ec 100644 --- a/akka-macros/src/main/scala/akka/channels/package.scala +++ b/akka-macros/src/main/scala/akka/channels/package.scala @@ -8,11 +8,6 @@ package object channels { trait Channel[I, O] sealed trait ChannelList - sealed trait ParentList - sealed trait TNil extends ChannelList with ParentList - + sealed trait TNil extends ChannelList sealed trait :=:[A <: Channel[_, _], B <: ChannelList] extends ChannelList - sealed trait :-:[A, B <: ParentList] extends ParentList - - trait Parent[T <: ParentList] } \ No newline at end of file From b004f1210e93ca7913321f9b3b821349590e2c3f Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 2 Jan 2013 21:22:49 +0100 Subject: [PATCH 04/26] replace Channel[] trait with simple Tuple2 for nicer syntax --- .../scala/akka/channels/ChannelSpec.scala | 36 +++++++++---------- .../main/scala/akka/channels/Channels.scala | 2 +- .../main/scala/akka/channels/package.scala | 6 ++-- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 7ceaa7c306..944169efea 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -29,7 +29,7 @@ object ChannelSpec { object D extends D // used for sender verification in the first two test cases - class Tester extends Channels[TNil, Channel[A, C] :=: Channel[B, D] :=: TNil] { + class Tester extends Channels[TNil, (A, C) :=: (B, D) :=: TNil] { channel[A.type] { case (A, s) ⇒ s ! C } @@ -37,12 +37,12 @@ object ChannelSpec { case (B, s) ⇒ s ! D } } - class RecvC(ref: ActorRef) extends Channels[TNil, Channel[C, Nothing] :=: TNil] { + class RecvC(ref: ActorRef) extends Channels[TNil, (C, Nothing) :=: TNil] { channel[C] { case (x, _) ⇒ ref ! x } } // pos compile test for multiple reply channels - class SubChannels extends Channels[TNil, Channel[A, B] :=: Channel[A, C] :=: TNil] { + class SubChannels extends Channels[TNil, (A, B) :=: (A, C) :=: TNil] { channel[A] { case (A1, x) ⇒ x ! B @@ -51,8 +51,8 @@ object ChannelSpec { } // pos compile test for children - class Children extends Channels[TNil, Channel[A, B] :=: Channel[C, D] :=: TNil] { - val c = createChild(new Channels[Channel[A, Nothing]:=: TNil, Channel[B, C]:=: TNil] { + class Children extends Channels[TNil, (A, B) :=: (C, D) :=: TNil] { + val c = createChild(new Channels[(A, Nothing) :=: TNil, (B, C) :=: TNil] { channel[B] { case (B, s) ⇒ s ! C } }) @@ -64,8 +64,8 @@ object ChannelSpec { case (C, _) ⇒ client ! C } - createChild(new Channels[Channel[C, Nothing]:=: TNil, TNil]) - createChild(new Channels[Channel[A, Nothing]:=: Channel[C, Nothing]:=: TNil, TNil]) + createChild(new Channels[(C, Nothing) :=: TNil, TNil]) + createChild(new Channels[(A, Nothing) :=:(C, Nothing) :=: TNil, TNil]) } } @@ -98,7 +98,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new ChannelRef[Channel[A, C] :=: TNil](null) ! B + |new ChannelRef[(A, C) :=: TNil](null) ! B """.stripMargin) }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.B.type") } @@ -108,7 +108,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new ChannelRef[Channel[A, C] :=: Channel[B, D] :=: TNil](null) ! C + |new ChannelRef[(A, C) :=: (B, D) :=: TNil](null) ! C """.stripMargin) }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.C.type") } @@ -118,15 +118,15 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |implicit val s = new ChannelRef[Channel[C, D] :=: TNil](null) - |new ChannelRef[Channel[A, B] :=: TNil](null) ! A + |implicit val s = new ChannelRef[(C, D) :=: TNil](null) + |new ChannelRef[(A, B) :=: TNil](null) ! A """.stripMargin) }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B") } "permit any sender for Nothing replies" in { implicit val s = new ChannelRef[TNil](testActor) - new ChannelRef[Channel[A, Nothing]:=: TNil](testActor) ! A + new ChannelRef[(A, Nothing) :=: TNil](testActor) ! A expectMsg(A) } @@ -136,7 +136,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { |import akka.channels._ |import ChannelSpec._ |implicit val s = new ChannelRef[TNil](null) - |new ChannelRef[Channel[A, B] :=: Channel[A, C] :=: TNil](null) ! A + |new ChannelRef[(A, B) :=: (A, C) :=: TNil](null) ! A """.stripMargin) }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B, akka.channels.ChannelSpec.C") } @@ -146,7 +146,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, Channel[A, B] :=: TNil] { + |new Channels[TNil, (A, B) :=: TNil] { | channel[B] { | case (B, _) => | } @@ -160,7 +160,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, Channel[A, B] :=: Channel[A1.type, C] :=: TNil] { + |new Channels[TNil, (A, B) :=: (A1.type, C) :=: TNil] { | channel[A] { | case (A1, x) => x ! C | } @@ -174,7 +174,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, Channel[A, B] :=: Channel[C, D] :=: TNil] { + |new Channels[TNil, (A, B) :=: (C, D) :=: TNil] { | createChild(new Channels) |} """.stripMargin) @@ -186,8 +186,8 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, Channel[A, B] :=: Channel[C, D] :=: TNil] { - | createChild(new Channels[Channel[B, Nothing] :=: TNil, TNil]) + |new Channels[TNil, (A, B) :=: (C, D) :=: TNil] { + | createChild(new Channels[(B, Nothing) :=: TNil, TNil]) |} """.stripMargin) }.message must include("This actor cannot support a child requiring channels akka.channels.ChannelSpec.B") diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index 41bac0fb63..51f6554a71 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -221,7 +221,7 @@ object Channels { case head :: (tail: List[Type]) ⇒ rec(tail, appliedType(weakTypeOf[:=:[_, _]].typeConstructor, List( - appliedType(weakTypeOf[Channel[_, _]].typeConstructor, List( + appliedType(weakTypeOf[Tuple2[_, _]].typeConstructor, List( head, weakTypeOf[Nothing])), acc))) diff --git a/akka-macros/src/main/scala/akka/channels/package.scala b/akka-macros/src/main/scala/akka/channels/package.scala index a9734975ec..d8cc07fdf2 100644 --- a/akka-macros/src/main/scala/akka/channels/package.scala +++ b/akka-macros/src/main/scala/akka/channels/package.scala @@ -5,9 +5,7 @@ package akka package object channels { - trait Channel[I, O] - sealed trait ChannelList sealed trait TNil extends ChannelList - sealed trait :=:[A <: Channel[_, _], B <: ChannelList] extends ChannelList -} \ No newline at end of file + sealed trait :=:[A <: Tuple2[_, _], B <: ChannelList] extends ChannelList +} From 363fa71f4b5bb01713ac21238b64bb92e3480707 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 3 Jan 2013 18:17:43 +0100 Subject: [PATCH 05/26] change Recv[] from partial to total function - also change :=: to :+: for faster typing pleasure on US keyboards --- .../scala/akka/channels/ChannelSpec.scala | 50 +++++++++++-------- .../main/scala/akka/channels/Channels.scala | 8 +-- .../main/scala/akka/channels/package.scala | 2 +- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 944169efea..a4adf79a31 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -24,25 +24,25 @@ object ChannelSpec { trait C extends Msg object C extends C + object C1 extends C trait D extends Msg object D extends D // used for sender verification in the first two test cases - class Tester extends Channels[TNil, (A, C) :=: (B, D) :=: TNil] { - channel[A.type] { - case (A, s) ⇒ s ! C - } + class Tester extends Channels[TNil, (A, C) :+: (B, D) :+: TNil] { + channel[A.type] { (msg, snd) ⇒ snd ! C } + channel[A] { (msg, snd) ⇒ snd ! C1 } channel[B] { case (B, s) ⇒ s ! D } } - class RecvC(ref: ActorRef) extends Channels[TNil, (C, Nothing) :=: TNil] { + class RecvC(ref: ActorRef) extends Channels[TNil, (C, Nothing) :+: TNil] { channel[C] { case (x, _) ⇒ ref ! x } } // pos compile test for multiple reply channels - class SubChannels extends Channels[TNil, (A, B) :=: (A, C) :=: TNil] { + class SubChannels extends Channels[TNil, (A, B) :+: (A, C) :+: TNil] { channel[A] { case (A1, x) ⇒ x ! B @@ -51,8 +51,8 @@ object ChannelSpec { } // pos compile test for children - class Children extends Channels[TNil, (A, B) :=: (C, D) :=: TNil] { - val c = createChild(new Channels[(A, Nothing) :=: TNil, (B, C) :=: TNil] { + class Children extends Channels[TNil, (A, B) :+: (C, D) :+: TNil] { + val c = createChild(new Channels[(A, Nothing) :+: TNil, (B, C) :+: TNil] { channel[B] { case (B, s) ⇒ s ! C } }) @@ -64,8 +64,8 @@ object ChannelSpec { case (C, _) ⇒ client ! C } - createChild(new Channels[(C, Nothing) :=: TNil, TNil]) - createChild(new Channels[(A, Nothing) :=:(C, Nothing) :=: TNil, TNil]) + createChild(new Channels[(C, Nothing) :+: TNil, TNil]) + createChild(new Channels[(A, Nothing) :+:(C, Nothing) :+: TNil, TNil]) } } @@ -92,13 +92,21 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { expectMsg(C) lastSender must be(sender.actorRef) } + + "correctly dispatch to subchannels" in { + val ref = ChannelExt(system).actorOf(new Tester) + implicit val sender = ChannelExt(system).actorOf(new RecvC(testActor)) + ref ! A2 + expectMsg(C1) + lastSender must be(sender.actorRef) + } "not permit wrong message type" in { intercept[ToolBoxError] { eval(""" |import akka.channels._ |import ChannelSpec._ - |new ChannelRef[(A, C) :=: TNil](null) ! B + |new ChannelRef[(A, C) :+: TNil](null) ! B """.stripMargin) }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.B.type") } @@ -108,7 +116,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new ChannelRef[(A, C) :=: (B, D) :=: TNil](null) ! C + |new ChannelRef[(A, C) :+: (B, D) :+: TNil](null) ! C """.stripMargin) }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.C.type") } @@ -118,15 +126,15 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |implicit val s = new ChannelRef[(C, D) :=: TNil](null) - |new ChannelRef[(A, B) :=: TNil](null) ! A + |implicit val s = new ChannelRef[(C, D) :+: TNil](null) + |new ChannelRef[(A, B) :+: TNil](null) ! A """.stripMargin) }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B") } "permit any sender for Nothing replies" in { implicit val s = new ChannelRef[TNil](testActor) - new ChannelRef[(A, Nothing) :=: TNil](testActor) ! A + new ChannelRef[(A, Nothing) :+: TNil](testActor) ! A expectMsg(A) } @@ -136,7 +144,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { |import akka.channels._ |import ChannelSpec._ |implicit val s = new ChannelRef[TNil](null) - |new ChannelRef[(A, B) :=: (A, C) :=: TNil](null) ! A + |new ChannelRef[(A, B) :+: (A, C) :+: TNil](null) ! A """.stripMargin) }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B, akka.channels.ChannelSpec.C") } @@ -146,7 +154,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (A, B) :=: TNil] { + |new Channels[TNil, (A, B) :+: TNil] { | channel[B] { | case (B, _) => | } @@ -160,7 +168,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (A, B) :=: (A1.type, C) :=: TNil] { + |new Channels[TNil, (A, B) :+: (A1.type, C) :+: TNil] { | channel[A] { | case (A1, x) => x ! C | } @@ -174,7 +182,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (A, B) :=: (C, D) :=: TNil] { + |new Channels[TNil, (A, B) :+: (C, D) :+: TNil] { | createChild(new Channels) |} """.stripMargin) @@ -186,8 +194,8 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (A, B) :=: (C, D) :=: TNil] { - | createChild(new Channels[(B, Nothing) :=: TNil, TNil]) + |new Channels[TNil, (A, B) :+: (C, D) :+: TNil] { + | createChild(new Channels[(B, Nothing) :+: TNil, TNil]) |} """.stripMargin) }.message must include("This actor cannot support a child requiring channels akka.channels.ChannelSpec.B") diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index 51f6554a71..75377de9e2 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -96,7 +96,7 @@ class Channels[P <: ChannelList, C <: ChannelList: TypeTag] extends Actor { val msgClass = x.getClass index find (_ isAssignableFrom msgClass) match { case None ⇒ default(x) - case Some(cls) ⇒ behavior(cls).applyOrElse((x, new ChannelRef(sender)), (pair: (A, ChannelRef[C])) ⇒ default(pair._1)) + case Some(cls) ⇒ behavior(cls).apply(x, new ChannelRef(sender)) } } @@ -104,7 +104,7 @@ class Channels[P <: ChannelList, C <: ChannelList: TypeTag] extends Actor { val msgClass = x.getClass index find (_ isAssignableFrom msgClass) match { case None ⇒ false - case Some(cls) ⇒ behavior(cls).isDefinedAt((x, new ChannelRef(sender))) + case Some(cls) ⇒ true } } } @@ -112,7 +112,7 @@ class Channels[P <: ChannelList, C <: ChannelList: TypeTag] extends Actor { object Channels { - type Recv[T, Ch <: ChannelList] = PartialFunction[(T, ChannelRef[Ch]), Unit] + type Recv[T, Ch <: ChannelList] = Function2[T, ChannelRef[Ch], Unit] /** * This macro transforms a channel[] call which returns “some” Behaviorist @@ -220,7 +220,7 @@ object Channels { def rec(l: List[Type], acc: Type): Type = l match { case head :: (tail: List[Type]) ⇒ rec(tail, - appliedType(weakTypeOf[:=:[_, _]].typeConstructor, List( + appliedType(weakTypeOf[:+:[_, _]].typeConstructor, List( appliedType(weakTypeOf[Tuple2[_, _]].typeConstructor, List( head, weakTypeOf[Nothing])), diff --git a/akka-macros/src/main/scala/akka/channels/package.scala b/akka-macros/src/main/scala/akka/channels/package.scala index d8cc07fdf2..d44dbae450 100644 --- a/akka-macros/src/main/scala/akka/channels/package.scala +++ b/akka-macros/src/main/scala/akka/channels/package.scala @@ -7,5 +7,5 @@ package akka package object channels { sealed trait ChannelList sealed trait TNil extends ChannelList - sealed trait :=:[A <: Tuple2[_, _], B <: ChannelList] extends ChannelList + sealed trait :+:[A <: (_, _), B <: ChannelList] extends ChannelList } From cc2cda8752ed49dba0b6519f5ff95d465d870065 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 3 Jan 2013 19:53:22 +0100 Subject: [PATCH 06/26] add tests for parentChannel --- .../scala/akka/channels/ChannelSpec.scala | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index a4adf79a31..e597edf754 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -92,7 +92,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { expectMsg(C) lastSender must be(sender.actorRef) } - + "correctly dispatch to subchannels" in { val ref = ChannelExt(system).actorOf(new Tester) implicit val sender = ChannelExt(system).actorOf(new RecvC(testActor)) @@ -207,6 +207,30 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { expectMsg(C) } + "have a working parentChannel" in { + val parent = ChannelExt(system).actorOf(new Channels[TNil, (A, Nothing) :+: TNil] { + createChild(new Channels[(A, Nothing) :+: TNil, TNil] { + parentChannel ! A + }) + channel[A] { (msg, snd) ⇒ testActor ! msg } + }) + expectMsg(A) + } + + "not permit sending wrong things to parents" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new Channels[TNil, (A, Nothing) :+: TNil] { + | createChild(new Channels[(A, Nothing) :+: TNil, TNil] { + | parentChannel ! B + | }) + |} + """.stripMargin) + }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.B.type") + } + } } \ No newline at end of file From 4b2a28887dadbf1ae397fbe21919ad30813dfe27 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 3 Jan 2013 21:11:20 +0100 Subject: [PATCH 07/26] add ChannelRef.narrow[] --- .../scala/akka/channels/ChannelSpec.scala | 45 +++++++++++++++++++ .../main/scala/akka/channels/ChannelRef.scala | 21 +++++++++ .../main/scala/akka/channels/Channels.scala | 4 +- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index e597edf754..61901bbe4f 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -231,6 +231,51 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.B.type") } + "support narrowing of references" in { + val ref = new ChannelRef[(A, B) :+:(C, D) :+: TNil](null) + val n: ChannelRef[(A1.type, B) :+: TNil] = ref.narrow[(A1.type, B) :+: TNil] + } + + "not allow narrowed refs to open new channels" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new ChannelRef[(A, C) :+: TNil](null).narrow[(A, C) :+: (B, C) :+: TNil] + """.stripMargin) + }.message must include("original ChannelRef does not support input type akka.channels.ChannelSpec.B") + } + + "not allow narrowed refs to widen channels" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new ChannelRef[(A1.type, C) :+: TNil](null).narrow[(A, C) :+: TNil] + """.stripMargin) + }.message must include("original ChannelRef does not support input type akka.channels.ChannelSpec.A") + } + + "not allow narrowed refs to miss reply channels" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new ChannelRef[(A, C) :+: (A, D) :+: TNil](null).narrow[(A, C) :+: TNil] + """.stripMargin) + }.message must include("reply types akka.channels.ChannelSpec.D not covered for channel akka.channels.ChannelSpec.A") + } + + "not allow narrowed refs to narrow reply channels" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new ChannelRef[(A, C) :+: (B, D) :+: TNil](null).narrow[(A, C) :+: (A, Nothing) :+: TNil] + """.stripMargin) + }.message must include("reply types Nothing are superfluous for channel akka.channels.ChannelSpec.A") + } + } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala index 3a6ff2ad1d..ed264f97ba 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala @@ -16,6 +16,8 @@ class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { def ![M](msg: M): Unit = macro ChannelRef.tell[T, M] + def narrow[C <: ChannelList]: ChannelRef[C] = macro ChannelRef.narrowImpl[C, T] + } object ChannelRef { @@ -52,4 +54,23 @@ object ChannelRef { } } + def narrowImpl[C <: ChannelList: c.WeakTypeTag, T <: ChannelList: c.WeakTypeTag]( + c: Context { + type PrefixType = ChannelRef[T] + }): c.Expr[ChannelRef[C]] = { + import c.{ universe ⇒ u } + for (in ← inputChannels(u)(u.weakTypeOf[C])) { + val replies = replyChannels(u)(u.weakTypeOf[T], in) + if (replies.isEmpty) c.error(c.enclosingPosition, s"original ChannelRef does not support input type $in") + else { + val targetReplies = replyChannels(u)(u.weakTypeOf[C], in) + val unsatisfied = replies filterNot (r ⇒ targetReplies exists (r <:< _)) + if (unsatisfied.nonEmpty) c.error(c.enclosingPosition, s"reply types ${unsatisfied mkString ", "} not covered for channel $in") + val leftovers = targetReplies filterNot (t ⇒ replies exists (_ <:< t)) + if (leftovers.nonEmpty) c.error(c.enclosingPosition, s"reply types ${leftovers mkString ", "} are superfluous for channel $in") + } + } + u.reify(c.prefix.splice.asInstanceOf[ChannelRef[C]]) + } + } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index 75377de9e2..dc149211f0 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -172,7 +172,7 @@ object Channels { final def inputChannels(u: Universe)(list: u.Type): List[u.Type] = { import u._ def rec(l: u.Type, acc: List[u.Type]): List[u.Type] = l match { - case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, in :: acc) + case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, if (acc contains in) acc else in :: acc) case _ ⇒ acc.reverse } rec(list, Nil) @@ -187,7 +187,7 @@ object Channels { def rec(l: Type, acc: List[Type]): List[Type] = { l match { case TypeRef(_, _, TypeRef(_, _, in :: out :: Nil) :: tail :: Nil) if msg <:< in ⇒ - rec(tail, out :: acc) + rec(tail, if (acc contains out) acc else out :: acc) case TypeRef(_, _, _ :: tail :: Nil) ⇒ rec(tail, acc) case _ ⇒ acc.reverse From da10291cdd7a4b3cb145faec6d8aafb6d15b0ed9 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 3 Jan 2013 23:36:44 +0100 Subject: [PATCH 08/26] add narrowing from ActorRef to ChannelRef --- .../scala/akka/channels/ChannelSpec.scala | 37 +++++- .../akka/channels/ChannelExtension.scala | 4 + .../main/scala/akka/channels/ChannelRef.scala | 14 +-- .../main/scala/akka/channels/Channels.scala | 113 ++++++++++++++---- 4 files changed, 128 insertions(+), 40 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 61901bbe4f..0049d612d2 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -4,11 +4,14 @@ package akka.channels -import akka.testkit.AkkaSpec -import akka.testkit.ImplicitSender +import akka.testkit._ import akka.actor.ActorRef import akka.makkros.Test._ import scala.tools.reflect.ToolBoxError +import akka.util.Timeout +import scala.concurrent.duration._ +import scala.concurrent.Await +import scala.util.Failure object ChannelSpec { @@ -64,8 +67,8 @@ object ChannelSpec { case (C, _) ⇒ client ! C } - createChild(new Channels[(C, Nothing) :+: TNil, TNil]) - createChild(new Channels[(A, Nothing) :+:(C, Nothing) :+: TNil, TNil]) + createChild(new Channels[(C, Nothing) :+: TNil, TNil] {}) + createChild(new Channels[(A, Nothing) :+:(C, Nothing) :+: TNil, TNil] {}) } } @@ -183,7 +186,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { |import akka.channels._ |import ChannelSpec._ |new Channels[TNil, (A, B) :+: (C, D) :+: TNil] { - | createChild(new Channels) + | createChild(new Channels[Nothing, Nothing] {}) |} """.stripMargin) }.message must include("Parent argument must not be Nothing") @@ -195,7 +198,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { |import akka.channels._ |import ChannelSpec._ |new Channels[TNil, (A, B) :+: (C, D) :+: TNil] { - | createChild(new Channels[(B, Nothing) :+: TNil, TNil]) + | createChild(new Channels[(B, Nothing) :+: TNil, TNil] {}) |} """.stripMargin) }.message must include("This actor cannot support a child requiring channels akka.channels.ChannelSpec.B") @@ -276,6 +279,28 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { }.message must include("reply types Nothing are superfluous for channel akka.channels.ChannelSpec.A") } + "support narrowing ActorRefs" in { + import Channels._ + val channel = ChannelExt(system).actorOf(new RecvC(testActor)) + val ref = channel.actorRef + implicit val t = Timeout(1.second.dilated) + import system.dispatcher + val r = Await.result(ref.narrow[(C, Nothing) :+: TNil], t.duration) + r ! C + expectMsg(C) + } + + "deny wrong narrowing of ActorRefs" in { + import Channels._ + val channel = ChannelExt(system).actorOf(new RecvC(testActor)) + val ref = channel.actorRef + implicit val t = Timeout(1.second.dilated) + import system.dispatcher + val f = ref.narrow[(D, Nothing) :+: TNil] + Await.ready(f, t.duration) + f.value.get must be(Failure(Channels.NarrowingException("original ChannelRef does not support input type akka.channels.ChannelSpec.D"))) + } + } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala b/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala index bd18f43e0c..6d3d60226d 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala @@ -15,6 +15,10 @@ import scala.reflect.runtime.universe object ChannelExt extends ExtensionKey[ChannelExtension] class ChannelExtension(system: ExtendedActorSystem) extends Extension { + + // kick-start the universe (needed due to thread safety issues in runtime mirror) + private val t = implicitly[TypeTag[(Int, Int) :+: TNil]] + def actorOf[Ch <: ChannelList: TypeTag](factory: ⇒ Channels[_, Ch]): ChannelRef[Ch] = new ChannelRef[Ch](system.actorOf(Props(factory))) } diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala index ed264f97ba..13e91ada2f 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala @@ -59,16 +59,10 @@ object ChannelRef { type PrefixType = ChannelRef[T] }): c.Expr[ChannelRef[C]] = { import c.{ universe ⇒ u } - for (in ← inputChannels(u)(u.weakTypeOf[C])) { - val replies = replyChannels(u)(u.weakTypeOf[T], in) - if (replies.isEmpty) c.error(c.enclosingPosition, s"original ChannelRef does not support input type $in") - else { - val targetReplies = replyChannels(u)(u.weakTypeOf[C], in) - val unsatisfied = replies filterNot (r ⇒ targetReplies exists (r <:< _)) - if (unsatisfied.nonEmpty) c.error(c.enclosingPosition, s"reply types ${unsatisfied mkString ", "} not covered for channel $in") - val leftovers = targetReplies filterNot (t ⇒ replies exists (_ <:< t)) - if (leftovers.nonEmpty) c.error(c.enclosingPosition, s"reply types ${leftovers mkString ", "} are superfluous for channel $in") - } + narrowCheck(u)(u.weakTypeOf[T], u.weakTypeOf[C]) match { + case Nil ⇒ // okay + case err :: Nil ⇒ c.error(c.enclosingPosition, err) + case list ⇒ c.error(c.enclosingPosition, list mkString ("multiple errors:\n - ", "\n - ", "")) } u.reify(c.prefix.splice.asInstanceOf[ChannelRef[C]]) } diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index dc149211f0..84f05f91cc 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -5,15 +5,21 @@ package akka.channels import language.experimental.macros -import akka.actor.Actor +import akka.actor.{ Actor, ActorRef } import scala.reflect.macros.Context +import scala.reflect.runtime.{ universe ⇒ ru } import scala.reflect.runtime.universe.TypeTag -import scala.reflect.macros.Universe +import scala.reflect.api.Universe import scala.runtime.AbstractPartialFunction import akka.actor.Props import scala.collection.immutable import scala.collection.mutable.ArrayBuffer import scala.reflect.{ classTag, ClassTag } +import scala.concurrent.{ ExecutionContext, Future } +import akka.util.Timeout +import akka.pattern.ask +import scala.util.control.NoStackTrace +import akka.AkkaException /** * Typed channels atop untyped actors. @@ -32,7 +38,7 @@ import scala.reflect.{ classTag, ClassTag } * erased type, which may be less precise than the actual channel type; this * can lead to ClassCastExceptions if sending through the untyped ActorRef */ -class Channels[P <: ChannelList, C <: ChannelList: TypeTag] extends Actor { +trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { import Channels._ @@ -75,6 +81,14 @@ class Channels[P <: ChannelList, C <: ChannelList: TypeTag] extends Actor { behavior += cls -> recv.asInstanceOf[Recv[Any, ChannelList]] } + /* + * HORRIBLE HACK AHEAD + * + * I’d like to keep this a trait, but traits cannot have constructor + * arguments, not even TypeTags. + */ + protected var channelListTypeTag: TypeTag[C] = _ + /** * Sort so that subtypes always precede their supertypes, but without * obeying any order between unrelated subtypes (insert sort). @@ -92,20 +106,29 @@ class Channels[P <: ChannelList, C <: ChannelList: TypeTag] extends Actor { val index = sortClasses(behavior.keys) - override def applyOrElse[A, B >: Unit](x: A, default: A ⇒ B): B = { - val msgClass = x.getClass - index find (_ isAssignableFrom msgClass) match { - case None ⇒ default(x) - case Some(cls) ⇒ behavior(cls).apply(x, new ChannelRef(sender)) - } + override def applyOrElse[A, B >: Unit](x: A, default: A ⇒ B): B = x match { + case CheckType(tt) ⇒ + narrowCheck(ru)(channelListTypeTag.tpe, tt.tpe) match { + case Nil ⇒ sender ! CheckTypeACK + case err :: Nil ⇒ sender ! CheckTypeNAK(err) + case list ⇒ sender ! CheckTypeNAK(list mkString ("multiple errors:\n - ", " - ", "")) + } + case _ ⇒ + val msgClass = x.getClass + index find (_ isAssignableFrom msgClass) match { + case None ⇒ default(x) + case Some(cls) ⇒ behavior(cls).apply(x, new ChannelRef(sender)) + } } - def isDefinedAt(x: Any): Boolean = { - val msgClass = x.getClass - index find (_ isAssignableFrom msgClass) match { - case None ⇒ false - case Some(cls) ⇒ true - } + def isDefinedAt(x: Any): Boolean = x match { + case c: CheckType[_] ⇒ true + case _ ⇒ + val msgClass = x.getClass + index find (_ isAssignableFrom msgClass) match { + case None ⇒ false + case Some(cls) ⇒ true + } } } } @@ -114,6 +137,11 @@ object Channels { type Recv[T, Ch <: ChannelList] = Function2[T, ChannelRef[Ch], Unit] + case class CheckType[T](tt: TypeTag[T]) + case object CheckTypeACK + case class CheckTypeNAK(errors: String) + case class NarrowingException(errors: String) extends AkkaException(errors) with NoStackTrace + /** * This macro transforms a channel[] call which returns “some” Behaviorist * into a _channel[] call with precise reply channel descriptors, so that the @@ -131,16 +159,26 @@ object Channels { reify(null) } else { val channels = toChannels(c.universe)(out) - c.Expr(Apply( - TypeApply( - Select(c.prefix.tree, "_channel"), List( - TypeTree().setType(c.weakTypeOf[T]), - TypeTree().setType(channels))), - List(Select( + c.Expr( + Apply( TypeApply( - Select(Select(Ident("scala"), "reflect"), "classTag"), - List(TypeTree().setType(c.weakTypeOf[T]))), - "runtimeClass")))) + Select(c.prefix.tree, "_channel"), List( + TypeTree().setType(c.weakTypeOf[T]), + TypeTree().setType(channels))), + List( + Block(List( + If(reify(c.prefix.splice.channelListTypeTag == null).tree, + Apply( + Select(c.prefix.tree, "channelListTypeTag_$eq"), + List(TypeApply( + Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), + List(TypeTree().setType(c.weakTypeOf[C]))))), + c.literalUnit.tree)), + Select( + TypeApply( + Select(Select(Ident("scala"), "reflect"), "classTag"), + List(TypeTree().setType(c.weakTypeOf[T]))), + "runtimeClass"))))) } } @@ -166,6 +204,25 @@ object Channels { } } + /** + * check that the original ChannelList is a subtype of the target ChannelList; return a list or error strings + */ + def narrowCheck(u: Universe)(orig: u.Type, target: u.Type): List[String] = { + var errors = List.empty[String] + for (in ← inputChannels(u)(target)) { + val replies = replyChannels(u)(orig, in) + if (replies.isEmpty) errors ::= s"original ChannelRef does not support input type $in" + else { + val targetReplies = replyChannels(u)(target, in) + val unsatisfied = replies filterNot (r ⇒ targetReplies exists (r <:< _)) + if (unsatisfied.nonEmpty) errors ::= s"reply types ${unsatisfied mkString ", "} not covered for channel $in" + val leftovers = targetReplies filterNot (t ⇒ replies exists (_ <:< t)) + if (leftovers.nonEmpty) errors ::= s"desired reply types ${leftovers mkString ", "} are superfluous for channel $in" + } + } + errors.reverse + } + /** * get all required channels from a Parent[_] */ @@ -230,4 +287,12 @@ object Channels { rec(list.reverse, weakTypeOf[TNil]) } + implicit class ActorRefOps(val ref: ActorRef) extends AnyVal { + def narrow[C <: ChannelList](implicit timeout: Timeout, ec: ExecutionContext, tt: ru.TypeTag[C]): Future[ChannelRef[C]] = { + ref ? CheckType(tt) map { + case CheckTypeACK ⇒ new ChannelRef[C](ref) + case CheckTypeNAK(error) ⇒ throw NarrowingException(error) + } + } + } } \ No newline at end of file From df4a6f1dd9f85b73dc9825b96c0536a5570a9e89 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 6 Jan 2013 18:50:49 +0100 Subject: [PATCH 09/26] add polymorphic channels - channel[T <: ChannelList] takes a WrappedMessage[T] => Unit function because there cannot be a typed sender reference (which type would it have?) - ChannelRef[T] allows forwarding of WrappedMessage[T] (and nothing else; keeping the sender is essential) - move ActorRef.narrow implicit into the package object - do not accept top-level Channels which send to their parent --- .../scala/akka/channels/ChannelSpec.scala | 40 +++++- .../akka/channels/ChannelExtension.scala | 4 +- .../main/scala/akka/channels/ChannelRef.scala | 18 +++ .../main/scala/akka/channels/Channels.scala | 121 ++++++++++++------ .../main/scala/akka/channels/package.scala | 28 +++- 5 files changed, 163 insertions(+), 48 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 0049d612d2..c4e5805463 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -8,10 +8,13 @@ import akka.testkit._ import akka.actor.ActorRef import akka.makkros.Test._ import scala.tools.reflect.ToolBoxError +import scala.reflect.runtime.{ universe ⇒ ru } import akka.util.Timeout import scala.concurrent.duration._ import scala.concurrent.Await import scala.util.Failure +import akka.actor.ActorSystem +import scala.reflect.api.Universe object ChannelSpec { @@ -70,9 +73,15 @@ object ChannelSpec { createChild(new Channels[(C, Nothing) :+: TNil, TNil] {}) createChild(new Channels[(A, Nothing) :+:(C, Nothing) :+: TNil, TNil] {}) } + + // compile test for polymorphic actors + class WriteOnly[T <: ChannelList: ru.TypeTag](target: ChannelRef[T]) extends Channels[TNil, (D, D) :+: T] { + channel[D] { (d, snd) ⇒ snd ! d } + channel[T] { x ⇒ target forward x } + } } -class ChannelSpec extends AkkaSpec with ImplicitSender { +class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, classOf[AkkaSpec].getClassLoader)) with ImplicitSender { import ChannelSpec._ @@ -163,7 +172,7 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { | } |} """.stripMargin) - }.message must include("no channel defined for type akka.channels.ChannelSpec.B") + }.message must include("no channel defined for types akka.channels.ChannelSpec.B") } "not permit subchannel replies" in { @@ -220,6 +229,16 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { expectMsg(A) } + "not permit top-level Channels which send to parent" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |null.asInstanceOf[ChannelExtension].actorOf(new Channels[(A, A) :+: TNil, (A, Nothing) :+: TNil] {}) + """.stripMargin) + }.message must include("type mismatch") + } + "not permit sending wrong things to parents" in { intercept[ToolBoxError] { eval(""" @@ -291,7 +310,6 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { } "deny wrong narrowing of ActorRefs" in { - import Channels._ val channel = ChannelExt(system).actorOf(new RecvC(testActor)) val ref = channel.actorRef implicit val t = Timeout(1.second.dilated) @@ -301,6 +319,22 @@ class ChannelSpec extends AkkaSpec with ImplicitSender { f.value.get must be(Failure(Channels.NarrowingException("original ChannelRef does not support input type akka.channels.ChannelSpec.D"))) } + "be equal according to its actor" in { + val c1, c2 = new ChannelRef[TNil](testActor) + c1 must be === c2 + } + + "allow wrapping of ChannelRefs with pass-through" in { + val target = ChannelExt(system).actorOf(new RecvC(testActor)) + val wrap = ChannelExt(system).actorOf(new WriteOnly(target)) + wrap ! C + expectMsg(C) + lastSender must be(target.actorRef) + wrap ! D + expectMsg(D) + lastSender must be(wrap.actorRef) + } + } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala b/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala index 6d3d60226d..3a610c8933 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala @@ -17,8 +17,8 @@ object ChannelExt extends ExtensionKey[ChannelExtension] class ChannelExtension(system: ExtendedActorSystem) extends Extension { // kick-start the universe (needed due to thread safety issues in runtime mirror) - private val t = implicitly[TypeTag[(Int, Int) :+: TNil]] + private val t = typeTag[(Int, Int) :+: TNil] - def actorOf[Ch <: ChannelList: TypeTag](factory: ⇒ Channels[_, Ch]): ChannelRef[Ch] = + def actorOf[Ch <: ChannelList](factory: ⇒ Channels[TNil, Ch]): ChannelRef[Ch] = new ChannelRef[Ch](system.actorOf(Props(factory))) } diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala index 13e91ada2f..a194eb78b6 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala @@ -11,11 +11,14 @@ import scala.reflect.macros.Context import scala.annotation.tailrec import scala.reflect.macros.Universe import akka.actor.Actor +import akka.actor.ActorContext class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { def ![M](msg: M): Unit = macro ChannelRef.tell[T, M] + def forward[M](msg: M): Unit = macro ChannelRef.forward[T, M] + def narrow[C <: ChannelList]: ChannelRef[C] = macro ChannelRef.narrowImpl[C, T] } @@ -54,6 +57,21 @@ object ChannelRef { } } + def forward[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[T] + })(msg: c.Expr[M]): c.Expr[Unit] = { + import c.universe._ + if (weakTypeOf[M] =:= weakTypeOf[Values.WrappedMessage[T]]) { + reify( + c.prefix.splice.actorRef.forward( + msg.splice.asInstanceOf[Values.WrappedMessage[_]].value)( + c.Expr(Ident("implicitly"))(weakTypeTag[ActorContext]).splice)) + } else { + c.error(c.enclosingPosition, s"cannot forward message unless types match exactly") + reify(()) + } + } + def narrowImpl[C <: ChannelList: c.WeakTypeTag, T <: ChannelList: c.WeakTypeTag]( c: Context { type PrefixType = ChannelRef[T] diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index 84f05f91cc..a4405f2655 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -20,6 +20,7 @@ import akka.util.Timeout import akka.pattern.ask import scala.util.control.NoStackTrace import akka.AkkaException +import akka.actor.ExtendedActorSystem /** * Typed channels atop untyped actors. @@ -60,7 +61,22 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { */ implicit def selfChannel = new ChannelRef[C](self) - private var behavior = Map.empty[Class[_], Recv[Any, ChannelList]] + /* + * This map holds the current behavior for each erasure-tagged channel; the + * basic receive impl will dispatch incoming messages according to the most + * specific erased type in this map. + */ + private var behavior = Map.empty[Class[_], FF] + + private trait FF + private object FF { + def apply(x: Any): FF = x match { + case f: Function1[_, _] ⇒ F1(f.asInstanceOf[Values.WrappedMessage[ChannelList] ⇒ Unit]) + case f: Function2[_, _, _] ⇒ F2(f.asInstanceOf[(Any, ChannelRef[ChannelList]) ⇒ Unit]) + } + } + private case class F1(f: Values.WrappedMessage[ChannelList] ⇒ Unit) extends FF + private case class F2(f: (Any, ChannelRef[ChannelList]) ⇒ Unit) extends FF /** * Declare an input channel of the given type; the returned object takes a partial function: @@ -73,12 +89,13 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { * } * }}} */ - def channel[T]: Channels[P, C]#Behaviorist[T, _ <: ChannelList] = macro channelImpl[T, C, P, ChannelList] + def channel[T]: Channels[P, C]#Behaviorist[Nothing, T] = macro channelImpl[T, C, P] - protected def _channel[T, Ch <: ChannelList](cls: Class[_]) = new Behaviorist[T, Ch](cls) - protected class Behaviorist[T, Ch <: ChannelList](cls: Class[_]) { - def apply(recv: Recv[T, Ch]): Unit = - behavior += cls -> recv.asInstanceOf[Recv[Any, ChannelList]] + new Behaviorist(null) + + protected class Behaviorist[-R, Ch](tt: ru.TypeTag[Ch]) { + def apply(recv: R): Unit = + behavior ++= (for (t ← inputChannels(ru)(tt.tpe)) yield tt.mirror.runtimeClass(t.widen) -> FF(recv)) } /* @@ -116,8 +133,12 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { case _ ⇒ val msgClass = x.getClass index find (_ isAssignableFrom msgClass) match { - case None ⇒ default(x) - case Some(cls) ⇒ behavior(cls).apply(x, new ChannelRef(sender)) + case None ⇒ default(x) + case Some(cls) ⇒ + behavior(cls) match { + case F1(f) ⇒ f(new Values.WrappedMessage[ChannelList](x)) + case F2(f) ⇒ f(x, new ChannelRef(sender)) + } } } @@ -146,25 +167,45 @@ object Channels { * This macro transforms a channel[] call which returns “some” Behaviorist * into a _channel[] call with precise reply channel descriptors, so that the * partial function it is applied to can enjoy proper type checking. + * + * T is the message type + * C is the channel list of the enclosing Channels + * P is the parent channel list */ - def channelImpl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: ChannelList: c.WeakTypeTag, Ch <: ChannelList]( + def channelImpl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: ChannelList: c.WeakTypeTag]( c: Context { type PrefixType = Channels[P, C] - }): c.Expr[Channels[P, C]#Behaviorist[T, Ch]] = { + }): c.Expr[Channels[P, C]#Behaviorist[Nothing, T]] = { + + val tT = c.weakTypeOf[T] + val tC = c.weakTypeOf[C] import c.universe._ - val out = replyChannels(c.universe)(c.weakTypeOf[C], c.weakTypeOf[T]) - if (out.isEmpty) { - c.error(c.enclosingPosition, s"no channel defined for type ${c.weakTypeOf[T]}") + + val undefined = missingChannels(c.universe)(tC, inputChannels(c.universe)(tT)) + if (undefined.nonEmpty) { + c.error(c.enclosingPosition, s"no channel defined for types ${undefined mkString ", "}") reify(null) } else { - val channels = toChannels(c.universe)(out) + val receive = + if (tT <:< typeOf[ChannelList]) { + appliedType(typeOf[Function1[_, _]].typeConstructor, List( + appliedType(typeOf[Values.WrappedMessage[_]].typeConstructor, List(tT)), + typeOf[Unit])) + } else { + val channels = toChannels(c.universe)(replyChannels(c.universe)(tC, tT)) + appliedType(typeOf[Function2[_, _, _]].typeConstructor, List( + tT, + appliedType(typeOf[ChannelRef[_]].typeConstructor, List(channels)), + typeOf[Unit])) + } c.Expr( Apply( - TypeApply( - Select(c.prefix.tree, "_channel"), List( - TypeTree().setType(c.weakTypeOf[T]), - TypeTree().setType(channels))), + Select( + New(AppliedTypeTree(Select(c.prefix.tree, newTypeName("Behaviorist")), List( + TypeTree().setType(receive), + TypeTree().setType(tT)))), + nme.CONSTRUCTOR), List( Block(List( If(reify(c.prefix.splice.channelListTypeTag == null).tree, @@ -174,11 +215,9 @@ object Channels { Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), List(TypeTree().setType(c.weakTypeOf[C]))))), c.literalUnit.tree)), - Select( - TypeApply( - Select(Select(Ident("scala"), "reflect"), "classTag"), - List(TypeTree().setType(c.weakTypeOf[T]))), - "runtimeClass"))))) + TypeApply( + Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), + List(TypeTree().setType(tT))))))) } } @@ -224,15 +263,19 @@ object Channels { } /** - * get all required channels from a Parent[_] + * get all input channels from a ChannelList or return the given type */ final def inputChannels(u: Universe)(list: u.Type): List[u.Type] = { import u._ + val imp = u.mkImporter(ru) + val cl = imp.importType(ru.typeOf[ChannelList]) + val tnil = imp.importType(ru.typeOf[TNil]) def rec(l: u.Type, acc: List[u.Type]): List[u.Type] = l match { case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, if (acc contains in) acc else in :: acc) - case _ ⇒ acc.reverse + case last ⇒ if (last =:= tnil) acc.reverse else (last :: acc).reverse } - rec(list, Nil) + if (list <:< cl) rec(list, Nil) + else List(list) } /** @@ -261,8 +304,8 @@ object Channels { // making the top-level method recursive blows up the compiler (when compiling the macro itself) def rec(ch: Type, req: List[Type]): List[Type] = { ch match { - case TypeRef(_, _, TypeRef(_, _, in :: _) :: (tail: Type) :: Nil) ⇒ rec(tail, req filterNot (_ <:< in)) - case _ ⇒ req + case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, req filterNot (_ <:< in)) + case last ⇒ req filterNot (_ <:< last) } } rec(channels, required) @@ -276,23 +319,17 @@ object Channels { import u._ def rec(l: List[Type], acc: Type): Type = l match { case head :: (tail: List[Type]) ⇒ - rec(tail, - appliedType(weakTypeOf[:+:[_, _]].typeConstructor, List( - appliedType(weakTypeOf[Tuple2[_, _]].typeConstructor, List( - head, - weakTypeOf[Nothing])), - acc))) + if (head =:= weakTypeOf[Nothing]) rec(tail, acc) + else + rec(tail, + appliedType(weakTypeOf[:+:[_, _]].typeConstructor, List( + appliedType(weakTypeOf[Tuple2[_, _]].typeConstructor, List( + head, + weakTypeOf[Nothing])), + acc))) case _ ⇒ acc } rec(list.reverse, weakTypeOf[TNil]) } - implicit class ActorRefOps(val ref: ActorRef) extends AnyVal { - def narrow[C <: ChannelList](implicit timeout: Timeout, ec: ExecutionContext, tt: ru.TypeTag[C]): Future[ChannelRef[C]] = { - ref ? CheckType(tt) map { - case CheckTypeACK ⇒ new ChannelRef[C](ref) - case CheckTypeNAK(error) ⇒ throw NarrowingException(error) - } - } - } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/package.scala b/akka-macros/src/main/scala/akka/channels/package.scala index d44dbae450..bcd3930d8d 100644 --- a/akka-macros/src/main/scala/akka/channels/package.scala +++ b/akka-macros/src/main/scala/akka/channels/package.scala @@ -4,8 +4,34 @@ package akka +import language.implicitConversions +import akka.actor.ActorRef + package object channels { + implicit def actorRef2Ops(ref: ActorRef) = new Values.ActorRefOps(ref) +} + +package channels { + import akka.util.Timeout + import akka.pattern.ask + import scala.concurrent.{ ExecutionContext, Future } + import scala.reflect.runtime.{ universe ⇒ ru } + sealed trait ChannelList sealed trait TNil extends ChannelList sealed trait :+:[A <: (_, _), B <: ChannelList] extends ChannelList -} + + object Values { + class ActorRefOps(val ref: ActorRef) extends AnyVal { + def narrow[C <: ChannelList](implicit timeout: Timeout, ec: ExecutionContext, tt: ru.TypeTag[C]): Future[ChannelRef[C]] = { + import Channels._ + ref ? CheckType(tt) map { + case CheckTypeACK ⇒ new ChannelRef[C](ref) + case CheckTypeNAK(error) ⇒ throw NarrowingException(error) + } + } + } + + class WrappedMessage[T <: ChannelList](val value: Any) extends AnyVal + } +} \ No newline at end of file From 697a7f863bd4c1560402ae3d030b5640e626dbf3 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 6 Jan 2013 22:05:07 +0100 Subject: [PATCH 10/26] add typed ask support --- .../scala/akka/channels/ChannelSpec.scala | 15 ++++++++++ .../main/scala/akka/channels/ChannelRef.scala | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index c4e5805463..4461343548 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -15,6 +15,7 @@ import scala.concurrent.Await import scala.util.Failure import akka.actor.ActorSystem import scala.reflect.api.Universe +import scala.concurrent.Future object ChannelSpec { @@ -335,6 +336,20 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, lastSender must be(wrap.actorRef) } + "support typed ask" in { + val t = ChannelExt(system).actorOf(new Tester) + implicit val timeout = Timeout(1.second) + val r: Future[C] = t ? A + Await.result(r, 1.second) must be(C) + } + + "support typed ask with multiple reply channels" in { + val t = ChannelExt(system).actorOf(new SubChannels) + implicit val timeout = Timeout(1.second) + val r: Future[Msg] = t ? A1 + Await.result(r, 1.second) must be(B) + } + } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala index a194eb78b6..351fb067f0 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala @@ -12,11 +12,15 @@ import scala.annotation.tailrec import scala.reflect.macros.Universe import akka.actor.Actor import akka.actor.ActorContext +import scala.concurrent.Future +import akka.util.Timeout class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { def ![M](msg: M): Unit = macro ChannelRef.tell[T, M] + def ?[M](msg: M): Future[_] = macro ChannelRef.ask[T, M] + def forward[M](msg: M): Unit = macro ChannelRef.forward[T, M] def narrow[C <: ChannelList]: ChannelRef[C] = macro ChannelRef.narrowImpl[C, T] @@ -57,6 +61,31 @@ object ChannelRef { } } + def ask[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[T] + })(msg: c.Expr[M]): c.Expr[Future[_]] = { + import c.universe._ + + val out = replyChannels(c.universe)(weakTypeOf[T], weakTypeOf[M]) + if (out.isEmpty) { + c.error(c.enclosingPosition, s"This ChannelRef does not support messages of type ${weakTypeOf[M]}") + reify(null) + } else { + val timeout = c.inferImplicitValue(typeOf[Timeout]) + if (timeout.isEmpty) + c.error(c.enclosingPosition, s"no implicit akka.util.Timeout found") + val result = appliedType(weakTypeOf[Future[_]].typeConstructor, List(lub(out))) + c.Expr( + TypeApply( + Select( + reify(akka.pattern.ask( + c.prefix.splice.actorRef, msg.splice)( + c.Expr(timeout)(weakTypeTag[Timeout]).splice)).tree, + "asInstanceOf"), + List(TypeTree().setType(result)))) + } + } + def forward[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { type PrefixType = ChannelRef[T] })(msg: c.Expr[M]): c.Expr[Unit] = { From dc254d7ab1ec5a5311cc3d93e17262e12842d0e9 Mon Sep 17 00:00:00 2001 From: Roland Date: Sun, 6 Jan 2013 22:54:54 +0100 Subject: [PATCH 11/26] add check for same-erasure channels --- .../scala/akka/channels/ChannelSpec.scala | 19 ++++++++++++++++--- .../main/scala/akka/channels/Channels.scala | 8 ++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 4461343548..60ae14eb37 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -51,9 +51,8 @@ object ChannelSpec { // pos compile test for multiple reply channels class SubChannels extends Channels[TNil, (A, B) :+: (A, C) :+: TNil] { channel[A] { - case (A1, x) ⇒ - x ! B - x ! C + case (A1, x) ⇒ x ! B + case (_, x) ⇒ x ! C } } @@ -80,6 +79,7 @@ object ChannelSpec { channel[D] { (d, snd) ⇒ snd ! d } channel[T] { x ⇒ target forward x } } + } class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, classOf[AkkaSpec].getClassLoader)) with ImplicitSender { @@ -350,6 +350,19 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, Await.result(r, 1.second) must be(B) } + "check that channels do not erase to the same types" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |new Channels[TNil, (List[A], A) :+: (List[B], B) :+: TNil] { + | channel[List[A]] { (x, s) ⇒ } + | channel[List[B]] { (x, s) ⇒ } + |} + """.stripMargin) + }.message must include("overlaps with declared channels") + } + } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index a4405f2655..9bb87a6886 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -187,6 +187,7 @@ object Channels { c.error(c.enclosingPosition, s"no channel defined for types ${undefined mkString ", "}") reify(null) } else { + checkUnique(c.universe)(tT, tC) foreach (c.error(c.enclosingPosition, _)) val receive = if (tT <:< typeOf[ChannelList]) { appliedType(typeOf[Function1[_, _]].typeConstructor, List( @@ -243,6 +244,13 @@ object Channels { } } + def checkUnique(u: Universe)(channel: u.Type, list: u.Type): Option[String] = { + val channels = inputChannels(u)(list) groupBy (_.erasure) + val dupes = channels.get(channel.erasure).getOrElse(Nil).filterNot(_ =:= channel) + if (dupes.isEmpty) None + else Some(s"erasure ${channel.erasure} overlaps with declared channels ${dupes mkString ", "}") + } + /** * check that the original ChannelList is a subtype of the target ChannelList; return a list or error strings */ From 74f6dd789bee48ecbc367c276a3250b308184938 Mon Sep 17 00:00:00 2001 From: Roland Date: Mon, 7 Jan 2013 16:49:08 +0100 Subject: [PATCH 12/26] add check that all channels were declared --- .../scala/akka/channels/ChannelSpec.scala | 27 +++++++++++++++++++ .../main/scala/akka/channels/Channels.scala | 13 +++++++++ 2 files changed, 40 insertions(+) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 60ae14eb37..955686502b 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -16,6 +16,11 @@ import scala.util.Failure import akka.actor.ActorSystem import scala.reflect.api.Universe import scala.concurrent.Future +import akka.actor.ActorInitializationException +import akka.actor.Props +import akka.actor.Actor +import akka.actor.OneForOneStrategy +import akka.actor.SupervisorStrategy.Stop object ChannelSpec { @@ -80,6 +85,10 @@ object ChannelSpec { channel[T] { x ⇒ target forward x } } + class MissingChannel extends Channels[TNil, (A, A) :+: (B, B) :+: TNil] { + channel[A.type] { (_, _) ⇒ } + } + } class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, classOf[AkkaSpec].getClassLoader)) with ImplicitSender { @@ -363,6 +372,24 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, }.message must include("overlaps with declared channels") } + "check that all channels were declared" in { + EventFilter[ActorInitializationException](occurrences = 1) intercept { + system.actorOf(Props(new Actor { + context.actorOf(Props[MissingChannel]) + override val supervisorStrategy = OneForOneStrategy() { + case ex: ActorInitializationException ⇒ testActor ! ex.getCause; Stop + } + def receive = { + case _ ⇒ + } + })) + } + val m = expectMsgType[ActorInitializationException].getMessage + m must include("missing declarations for channels") + m must include("akka.channels.ChannelSpec.A") + m must include("akka.channels.ChannelSpec.B") + } + } } \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index 9bb87a6886..a2426d77b3 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -21,6 +21,7 @@ import akka.pattern.ask import scala.util.control.NoStackTrace import akka.AkkaException import akka.actor.ExtendedActorSystem +import akka.actor.ActorInitializationException /** * Typed channels atop untyped actors. @@ -123,6 +124,18 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { val index = sortClasses(behavior.keys) + if (channelListTypeTag != null) verifyCompleteness() + + private def verifyCompleteness() { + val channels = inputChannels(ru)(channelListTypeTag.tpe) + val classes = channels groupBy (e ⇒ channelListTypeTag.mirror.runtimeClass(e.widen)) + val missing = classes.keySet -- behavior.keySet + if (missing.nonEmpty) { + val m = missing.map(classes).flatten + throw ActorInitializationException(s"missing declarations for channels ${m mkString ", "}") + } + } + override def applyOrElse[A, B >: Unit](x: A, default: A ⇒ B): B = x match { case CheckType(tt) ⇒ narrowCheck(ru)(channelListTypeTag.tpe, tt.tpe) match { From cfcc9da9bc14615615d6dc3b3937e7df3f9a20e4 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 8 Jan 2013 12:08:41 +0100 Subject: [PATCH 13/26] add verification of ping-pong chains - basic principle is that if we get a reply of type T1 and our channels allow us to reply to such a message with type T2, then the target needs to support that as well, and so on. - due to this, forwarding will have to be restricted; but it might well be that forwarding will not be completely safe in any case --- .../scala/akka/channels/ChannelSpec.scala | 58 ++++++++++++---- .../main/scala/akka/channels/ChannelRef.scala | 66 ++++++++++++------- .../main/scala/akka/channels/Channels.scala | 3 +- 3 files changed, 89 insertions(+), 38 deletions(-) diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala index 955686502b..b334b7d6a8 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -21,6 +21,7 @@ import akka.actor.Props import akka.actor.Actor import akka.actor.OneForOneStrategy import akka.actor.SupervisorStrategy.Stop +import java.lang.reflect.InvocationTargetException object ChannelSpec { @@ -62,7 +63,7 @@ object ChannelSpec { } // pos compile test for children - class Children extends Channels[TNil, (A, B) :+: (C, D) :+: TNil] { + class Children extends Channels[TNil, (A, B) :+: (C, Nothing) :+: TNil] { val c = createChild(new Channels[(A, Nothing) :+: TNil, (B, C) :+: TNil] { channel[B] { case (B, s) ⇒ s ! C } }) @@ -95,6 +96,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, import ChannelSpec._ + implicit val selfChannel = new ChannelRef[(Any, Nothing) :+: TNil](testActor) + "Channels" must { "construct refs" in { @@ -109,18 +112,18 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "select return channels" in { val ref = ChannelExt(system).actorOf(new Tester) - implicit val sender = ChannelExt(system).actorOf(new RecvC(testActor)) + implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor)) ref ! A expectMsg(C) - lastSender must be(sender.actorRef) + lastSender must be(selfChannel.actorRef) } "correctly dispatch to subchannels" in { val ref = ChannelExt(system).actorOf(new Tester) - implicit val sender = ChannelExt(system).actorOf(new RecvC(testActor)) + implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor)) ref ! A2 expectMsg(C1) - lastSender must be(sender.actorRef) + lastSender must be(selfChannel.actorRef) } "not permit wrong message type" in { @@ -130,7 +133,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import ChannelSpec._ |new ChannelRef[(A, C) :+: TNil](null) ! B """.stripMargin) - }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.B.type") + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B.type") } "not permit wrong message type in complex channel" in { @@ -140,7 +143,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import ChannelSpec._ |new ChannelRef[(A, C) :+: (B, D) :+: TNil](null) ! C """.stripMargin) - }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.C.type") + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.C.type") } "not permit unfit sender ref" in { @@ -151,11 +154,11 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |implicit val s = new ChannelRef[(C, D) :+: TNil](null) |new ChannelRef[(A, B) :+: TNil](null) ! A """.stripMargin) - }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B") + }.message must include("implicit sender `s` does not support messages of the reply types akka.channels.ChannelSpec.B") } "permit any sender for Nothing replies" in { - implicit val s = new ChannelRef[TNil](testActor) + implicit val selfChannel = new ChannelRef[TNil](testActor) new ChannelRef[(A, Nothing) :+: TNil](testActor) ! A expectMsg(A) } @@ -168,7 +171,32 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |implicit val s = new ChannelRef[TNil](null) |new ChannelRef[(A, B) :+: (A, C) :+: TNil](null) ! A """.stripMargin) - }.message must include("The implicit sender `value s` does not support messages of the reply types akka.channels.ChannelSpec.B, akka.channels.ChannelSpec.C") + }.message must include("implicit sender `s` does not support messages of the reply types akka.channels.ChannelSpec.B, akka.channels.ChannelSpec.C") + } + + "verify ping-pong chains" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[(B, B) :+: TNil](null) + |new ChannelRef[(A, B) :+: (B, C) :+: TNil](null) ! A + """.stripMargin) + }.message must include("implicit sender `s` does not support messages of the reply types akka.channels.ChannelSpec.C") + } + + "tolerate infinite ping-pong" in { + val ex = intercept[InvocationTargetException] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[(B, B) :+: (C, B) :+: TNil](null) + |new ChannelRef[(A, B) :+: (B, C) :+: TNil](null) ! A + """.stripMargin) + } + def cause(ex: Throwable): Throwable = + if (ex.getCause == null) ex else cause(ex.getCause) + cause(ex).getClass must be(classOf[NullPointerException]) } "not permit nonsensical channel declarations" in { @@ -196,7 +224,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, | } |} """.stripMargin) - }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.C.type") + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.C.type") } "not permit Nothing children" in { @@ -260,7 +288,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, | }) |} """.stripMargin) - }.message must include("This ChannelRef does not support messages of type akka.channels.ChannelSpec.B.type") + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B.type") } "support narrowing of references" in { @@ -360,7 +388,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "check that channels do not erase to the same types" in { - intercept[ToolBoxError] { + val m = intercept[ToolBoxError] { eval(""" |import akka.channels._ |import ChannelSpec._ @@ -369,7 +397,9 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, | channel[List[B]] { (x, s) ⇒ } |} """.stripMargin) - }.message must include("overlaps with declared channels") + }.message + m must include("erasure List[Any] overlaps with declared channels List[akka.channels.ChannelSpec.A]") + m must include("erasure List[Any] overlaps with declared channels List[akka.channels.ChannelSpec.B]") } "check that all channels were declared" in { diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala index 351fb067f0..aa78dc0c04 100644 --- a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala @@ -33,32 +33,52 @@ object ChannelRef { def tell[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { type PrefixType = ChannelRef[T] })(msg: c.Expr[M]): c.Expr[Unit] = { - val out = replyChannels(c.universe)(c.weakTypeOf[T], c.weakTypeOf[M]) - if (out.isEmpty) { - c.error(c.enclosingPosition, s"This ChannelRef does not support messages of type ${c.weakTypeOf[M]}") - return c.universe.reify(()) + import c.{ universe ⇒ u } + + def getSenderChannel = { + val replyChannel = c.inferImplicitValue(c.typeOf[ChannelRef[_]]) + if (!replyChannel.isEmpty) { + import u._ + replyChannel.tpe match { + case TypeRef(_, _, param :: Nil) ⇒ + Some((param, replyChannel, c.Expr(Select(replyChannel, "actorRef"))(u.weakTypeTag[ActorRef]))) + } + } else None } - val replyChannel = c.inferImplicitValue(c.typeOf[ChannelRef[_]]) - if (!replyChannel.isEmpty) { - import c.universe._ - val list = replyChannel.tpe match { - case TypeRef(_, _, param :: Nil) ⇒ param - } - val m = missingChannels(c.universe)(list, out) filterNot (_ =:= weakTypeOf[Nothing]) - if (m.isEmpty) { - val sender = c.Expr[ChannelRef[_]](replyChannel)(c.WeakTypeTag(replyChannel.tpe)) - c.universe.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice.actorRef)) - } else { - c.error(c.enclosingPosition, s"The implicit sender `${replyChannel.symbol}` does not support messages of the reply types ${m.mkString(", ")}") - c.universe.reify(()) - } - } else { + + def getSenderRef = { val senderTree = c.inferImplicitValue(c.typeOf[ActorRef]) - val sender = - if (senderTree.isEmpty) c.universe.reify(Actor.noSender) - else c.Expr(senderTree)(c.WeakTypeTag(senderTree.tpe)) - c.universe.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice)) + if (senderTree.isEmpty) { + val noSender = c.universe.reify(Actor.noSender) + ((u.typeOf[(Any, Nothing) :+: TNil], noSender.tree, noSender)) + } else ((u.typeOf[(Any, Any) :+: TNil], senderTree, c.Expr(senderTree)(c.WeakTypeTag(senderTree.tpe)))) } + + val tT = u.weakTypeOf[T] + val (tS, senderTree, sender) = getSenderChannel getOrElse getSenderRef + + def err(msg: String) = c.error(c.enclosingPosition, msg) + + def verify(msg: Set[u.Type], checked: Set[u.Type], depth: Int): Unit = if (msg.nonEmpty) { + val replies = msg map (m ⇒ m -> replyChannels(u)(tT, m)) + val missing = replies collect { case (k, v) if v.size == 0 ⇒ k } + if (missing.nonEmpty) + err(s"target ChannelRef does not support messages of types ${missing mkString ", "} (at depth $depth)") + else { + val nextSend = replies.map(_._2).flatten map (m ⇒ m -> replyChannels(u)(tS, m)) + val nextMissing = nextSend collect { case (k, v) if v.size == 0 ⇒ k } + if (nextMissing.nonEmpty) + err(s"implicit sender `$senderTree` does not support messages of the reply types ${nextMissing mkString ", "} (at depth $depth)") + else { + val nextChecked = checked ++ msg + val nextMsg = nextSend.map(_._2).flatten -- nextChecked + verify(nextMsg, nextChecked, depth + 1) + } + } + } + + verify(Set(u.weakTypeOf[M]), Set(u.typeOf[Nothing]), 1) + u.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice)) } def ask[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala index a2426d77b3..5c3b979ae3 100644 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ b/akka-macros/src/main/scala/akka/channels/Channels.scala @@ -314,7 +314,8 @@ object Channels { case _ ⇒ acc.reverse } } - rec(list, Nil) + val n = typeOf[Nothing] + if (msg =:= n) List(n) else rec(list, Nil) } /** From e862890deda853593dde438ef56e83e35626b288 Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 22 Jan 2013 22:50:09 +0100 Subject: [PATCH 14/26] major facelift: -!-> and -?-> appear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename projects to akka-channels and akka-channels-tests - move implementation into akka.channels.macros package - remove picking up ActorRef as sender (or none at all) - factor out logic to make different façades acting upon Future[] or Any so that -!-> and -?-> can complement the traditional <-!- and <-?- - the new operators are easily distinguishable from !/? and the rightwards-pointing go with the flow and compose better, let’s try them out --- .../src/main/scala/akka/dispatch/Future.scala | 13 + .../src/main/scala/akka/makkros/Test.scala | 2 +- .../scala/akka/channels/ChannelSpec.scala | 67 ++-- .../akka/channels/ChannelExtension.scala | 0 .../main/scala/akka/channels/ChannelRef.scala | 30 ++ .../main/scala/akka/channels/Channels.scala | 172 +++++++++ .../src/main/scala/akka/channels/Ops.scala | 36 ++ .../main/scala/akka/channels/macros/Ask.scala | 63 ++++ .../scala/akka/channels/macros/Channel.scala | 77 ++++ .../akka/channels/macros/CreateChild.scala | 35 ++ .../scala/akka/channels/macros/Helpers.scala | 119 ++++++ .../scala/akka/channels/macros/Narrow.scala | 24 ++ .../scala/akka/channels/macros/Tell.scala | 90 +++++ .../main/scala/akka/channels/package.scala | 15 + .../main/scala/akka/channels/ChannelRef.scala | 137 ------- .../main/scala/akka/channels/Channels.scala | 357 ------------------ .../main/scala/akka/channels/package.scala | 37 -- project/AkkaBuild.scala | 16 +- 18 files changed, 718 insertions(+), 572 deletions(-) rename {akka-macro-tests => akka-channels-tests}/src/main/scala/akka/makkros/Test.scala (90%) rename {akka-macro-tests => akka-channels-tests}/src/test/scala/akka/channels/ChannelSpec.scala (88%) rename {akka-macros => akka-channels}/src/main/scala/akka/channels/ChannelExtension.scala (100%) create mode 100644 akka-channels/src/main/scala/akka/channels/ChannelRef.scala create mode 100644 akka-channels/src/main/scala/akka/channels/Channels.scala create mode 100644 akka-channels/src/main/scala/akka/channels/Ops.scala create mode 100644 akka-channels/src/main/scala/akka/channels/macros/Ask.scala create mode 100644 akka-channels/src/main/scala/akka/channels/macros/Channel.scala create mode 100644 akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala create mode 100644 akka-channels/src/main/scala/akka/channels/macros/Helpers.scala create mode 100644 akka-channels/src/main/scala/akka/channels/macros/Narrow.scala create mode 100644 akka-channels/src/main/scala/akka/channels/macros/Tell.scala create mode 100644 akka-channels/src/main/scala/akka/channels/package.scala delete mode 100644 akka-macros/src/main/scala/akka/channels/ChannelRef.scala delete mode 100644 akka-macros/src/main/scala/akka/channels/Channels.scala delete mode 100644 akka-macros/src/main/scala/akka/channels/package.scala diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index a7c964b750..dbd8fbe0f0 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -62,6 +62,19 @@ object ExecutionContexts { * @return a reference to the global ExecutionContext */ def global(): ExecutionContext = ExecutionContext.global + + /** + * WARNING: Not A General Purpose ExecutionContext! + * + * This is an execution context which runs everything on the calling thread. + * It is very useful for actions which are known to be non-blocking and + * non-throwing in order to save a round-trip to the thread pool. + */ + object sameThreadExecutionContext extends ExecutionContext { + override def execute(runnable: Runnable): Unit = runnable.run() + override def reportFailure(t: Throwable): Unit = + throw new IllegalStateException("exception in sameThreadExecutionContext", t) + } } /** diff --git a/akka-macro-tests/src/main/scala/akka/makkros/Test.scala b/akka-channels-tests/src/main/scala/akka/makkros/Test.scala similarity index 90% rename from akka-macro-tests/src/main/scala/akka/makkros/Test.scala rename to akka-channels-tests/src/main/scala/akka/makkros/Test.scala index efb016175f..db241301c8 100644 --- a/akka-macro-tests/src/main/scala/akka/makkros/Test.scala +++ b/akka-channels-tests/src/main/scala/akka/makkros/Test.scala @@ -8,7 +8,7 @@ import scala.tools.reflect.ToolBoxError object Test { - def eval(code: String, compileOptions: String = "-cp akka-actor/target/classes:akka-macros/target/classes"): Any = { + 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)) } diff --git a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala similarity index 88% rename from akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala rename to akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index b334b7d6a8..783f59fb6e 100644 --- a/akka-macro-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -44,10 +44,10 @@ object ChannelSpec { // used for sender verification in the first two test cases class Tester extends Channels[TNil, (A, C) :+: (B, D) :+: TNil] { - channel[A.type] { (msg, snd) ⇒ snd ! C } - channel[A] { (msg, snd) ⇒ snd ! C1 } + channel[A.type] { (msg, snd) ⇒ snd <-!- C } + channel[A] { (msg, snd) ⇒ snd <-!- C1 } channel[B] { - case (B, s) ⇒ s ! D + case (B, s) ⇒ s <-!- D } } class RecvC(ref: ActorRef) extends Channels[TNil, (C, Nothing) :+: TNil] { @@ -57,20 +57,20 @@ object ChannelSpec { // pos compile test for multiple reply channels class SubChannels extends Channels[TNil, (A, B) :+: (A, C) :+: TNil] { channel[A] { - case (A1, x) ⇒ x ! B - case (_, x) ⇒ x ! C + case (A1, x) ⇒ B -!-> x + case (_, x) ⇒ x <-!- C } } // pos compile test for children class Children extends Channels[TNil, (A, B) :+: (C, Nothing) :+: TNil] { val c = createChild(new Channels[(A, Nothing) :+: TNil, (B, C) :+: TNil] { - channel[B] { case (B, s) ⇒ s ! C } + channel[B] { case (B, s) ⇒ s <-!- C } }) var client: ActorRef = _ channel[A] { - case (A, s) ⇒ c ! B; client = sender + case (A, s) ⇒ c <-!- B; client = sender } channel[C] { case (C, _) ⇒ client ! C @@ -81,9 +81,10 @@ object ChannelSpec { } // compile test for polymorphic actors - class WriteOnly[T <: ChannelList: ru.TypeTag](target: ChannelRef[T]) extends Channels[TNil, (D, D) :+: T] { - channel[D] { (d, snd) ⇒ snd ! d } - channel[T] { x ⇒ target forward x } + class WriteOnly[T1: ru.TypeTag, T2: ru.TypeTag](target: ChannelRef[(T1, T2) :+: TNil]) extends Channels[TNil, (D, D) :+: (T1, T2) :+: TNil] { + channel[D] { (d, snd) ⇒ snd <-!- d } + implicit val t = Timeout(1.second) + channel[T1] { (x, snd) ⇒ x -?-> target } } class MissingChannel extends Channels[TNil, (A, A) :+: (B, B) :+: TNil] { @@ -102,10 +103,10 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "construct refs" in { val ref = ChannelExt(system).actorOf(new Tester) - ref ! A + ref <-!- A expectMsg(C) lastSender must be(ref.actorRef) - ref ! B + ref <-!- B expectMsg(D) lastSender must be(ref.actorRef) } @@ -113,7 +114,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "select return channels" in { val ref = ChannelExt(system).actorOf(new Tester) implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor)) - ref ! A + ref <-!- A expectMsg(C) lastSender must be(selfChannel.actorRef) } @@ -121,7 +122,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "correctly dispatch to subchannels" in { val ref = ChannelExt(system).actorOf(new Tester) implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor)) - ref ! A2 + ref <-!- A2 expectMsg(C1) lastSender must be(selfChannel.actorRef) } @@ -131,7 +132,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, eval(""" |import akka.channels._ |import ChannelSpec._ - |new ChannelRef[(A, C) :+: TNil](null) ! B + |implicit val c = new ChannelRef[TNil](null) + |new ChannelRef[(A, C) :+: TNil](null) <-!- B """.stripMargin) }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B.type") } @@ -141,7 +143,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, eval(""" |import akka.channels._ |import ChannelSpec._ - |new ChannelRef[(A, C) :+: (B, D) :+: TNil](null) ! C + |implicit val c = new ChannelRef[TNil](null) + |new ChannelRef[(A, C) :+: (B, D) :+: TNil](null) <-!- C """.stripMargin) }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.C.type") } @@ -152,14 +155,14 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import akka.channels._ |import ChannelSpec._ |implicit val s = new ChannelRef[(C, D) :+: TNil](null) - |new ChannelRef[(A, B) :+: TNil](null) ! A + |new ChannelRef[(A, B) :+: TNil](null) <-!- A """.stripMargin) }.message must include("implicit sender `s` does not support messages of the reply types akka.channels.ChannelSpec.B") } "permit any sender for Nothing replies" in { implicit val selfChannel = new ChannelRef[TNil](testActor) - new ChannelRef[(A, Nothing) :+: TNil](testActor) ! A + new ChannelRef[(A, Nothing) :+: TNil](testActor) <-!- A expectMsg(A) } @@ -169,7 +172,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import akka.channels._ |import ChannelSpec._ |implicit val s = new ChannelRef[TNil](null) - |new ChannelRef[(A, B) :+: (A, C) :+: TNil](null) ! A + |new ChannelRef[(A, B) :+: (A, C) :+: TNil](null) <-!- A """.stripMargin) }.message must include("implicit sender `s` does not support messages of the reply types akka.channels.ChannelSpec.B, akka.channels.ChannelSpec.C") } @@ -180,7 +183,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import akka.channels._ |import ChannelSpec._ |implicit val s = new ChannelRef[(B, B) :+: TNil](null) - |new ChannelRef[(A, B) :+: (B, C) :+: TNil](null) ! A + |new ChannelRef[(A, B) :+: (B, C) :+: TNil](null) <-!- A """.stripMargin) }.message must include("implicit sender `s` does not support messages of the reply types akka.channels.ChannelSpec.C") } @@ -191,7 +194,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import akka.channels._ |import ChannelSpec._ |implicit val s = new ChannelRef[(B, B) :+: (C, B) :+: TNil](null) - |new ChannelRef[(A, B) :+: (B, C) :+: TNil](null) ! A + |new ChannelRef[(A, B) :+: (B, C) :+: TNil](null) <-!- A """.stripMargin) } def cause(ex: Throwable): Throwable = @@ -220,7 +223,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import ChannelSpec._ |new Channels[TNil, (A, B) :+: (A1.type, C) :+: TNil] { | channel[A] { - | case (A1, x) => x ! C + | case (A1, x) => x <-!- C | } |} """.stripMargin) @@ -253,14 +256,14 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "have a working selfChannel" in { val ref = ChannelExt(system).actorOf(new Children) - ref ! A + ref <-!- A expectMsg(C) } "have a working parentChannel" in { val parent = ChannelExt(system).actorOf(new Channels[TNil, (A, Nothing) :+: TNil] { createChild(new Channels[(A, Nothing) :+: TNil, TNil] { - parentChannel ! A + parentChannel <-!- A }) channel[A] { (msg, snd) ⇒ testActor ! msg } }) @@ -284,7 +287,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, |import ChannelSpec._ |new Channels[TNil, (A, Nothing) :+: TNil] { | createChild(new Channels[(A, Nothing) :+: TNil, TNil] { - | parentChannel ! B + | parentChannel <-!- B | }) |} """.stripMargin) @@ -343,7 +346,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, implicit val t = Timeout(1.second.dilated) import system.dispatcher val r = Await.result(ref.narrow[(C, Nothing) :+: TNil], t.duration) - r ! C + r <-!- C expectMsg(C) } @@ -354,7 +357,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, import system.dispatcher val f = ref.narrow[(D, Nothing) :+: TNil] Await.ready(f, t.duration) - f.value.get must be(Failure(Channels.NarrowingException("original ChannelRef does not support input type akka.channels.ChannelSpec.D"))) + f.value.get must be(Failure(NarrowingException("original ChannelRef does not support input type akka.channels.ChannelSpec.D"))) } "be equal according to its actor" in { @@ -364,11 +367,11 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "allow wrapping of ChannelRefs with pass-through" in { val target = ChannelExt(system).actorOf(new RecvC(testActor)) - val wrap = ChannelExt(system).actorOf(new WriteOnly(target)) - wrap ! C + val wrap = ChannelExt(system).actorOf(new WriteOnly[C, Nothing](target)) + wrap <-!- C expectMsg(C) lastSender must be(target.actorRef) - wrap ! D + wrap <-!- D expectMsg(D) lastSender must be(wrap.actorRef) } @@ -376,14 +379,14 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "support typed ask" in { val t = ChannelExt(system).actorOf(new Tester) implicit val timeout = Timeout(1.second) - val r: Future[C] = t ? A + val r: Future[C] = t <-?- A Await.result(r, 1.second) must be(C) } "support typed ask with multiple reply channels" in { val t = ChannelExt(system).actorOf(new SubChannels) implicit val timeout = Timeout(1.second) - val r: Future[Msg] = t ? A1 + val r: Future[Msg] = t <-?- A1 Await.result(r, 1.second) must be(B) } diff --git a/akka-macros/src/main/scala/akka/channels/ChannelExtension.scala b/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala similarity index 100% rename from akka-macros/src/main/scala/akka/channels/ChannelExtension.scala rename to akka-channels/src/main/scala/akka/channels/ChannelExtension.scala diff --git a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala new file mode 100644 index 0000000000..276371ece7 --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala @@ -0,0 +1,30 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.channels + +import language.experimental.{ macros ⇒ makkros } +import akka.actor.ActorRef +import scala.reflect.runtime.universe.TypeTag +import scala.reflect.macros.Context +import scala.annotation.tailrec +import scala.reflect.macros.Universe +import akka.actor.Actor +import akka.actor.ActorContext +import scala.concurrent.Future +import akka.util.Timeout +import akka.AkkaException +import scala.util.control.NoStackTrace + +case class NarrowingException(errors: String) extends AkkaException(errors) with NoStackTrace + +class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { + + def <-!-[M](msg: M): Unit = macro macros.Tell.impl[T, M] + + def <-?-[M](msg: M): Future[_] = macro macros.Ask.impl[T, M] + + def narrow[C <: ChannelList]: ChannelRef[C] = macro macros.Narrow.impl[C, T] + +} diff --git a/akka-channels/src/main/scala/akka/channels/Channels.scala b/akka-channels/src/main/scala/akka/channels/Channels.scala new file mode 100644 index 0000000000..65c9c9040c --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/Channels.scala @@ -0,0 +1,172 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka.channels + +import language.experimental.{ macros ⇒ makkros } +import akka.actor.{ Actor, ActorRef } +import scala.reflect.macros.Context +import scala.reflect.runtime.{ universe ⇒ ru } +import scala.reflect.runtime.universe.TypeTag +import scala.reflect.api.Universe +import scala.runtime.AbstractPartialFunction +import akka.actor.Props +import scala.collection.immutable +import scala.collection.mutable.ArrayBuffer +import scala.reflect.{ classTag, ClassTag } +import scala.concurrent.{ ExecutionContext, Future } +import akka.util.Timeout +import akka.pattern.ask +import scala.util.control.NoStackTrace +import akka.AkkaException +import akka.actor.ExtendedActorSystem +import akka.actor.ActorInitializationException + +/** + * Typed channels atop untyped actors. + * + * The idea is that the actor declares all its input types up front, including + * what it expects the sender to handle wrt.replies, and then ChannelRef + * carries this information for statically verifying that messages sent to an + * actor have an actual chance of being processed. + * + * There are several implementation-imposed restrictions: + * + * - not two channels with different input types may have the same erased + * type; this is currently not enforced at compile time (and leads to + * channels being “ignored” at runtime) + * - messages received by the actor are dispatched to channels based on the + * erased type, which may be less precise than the actual channel type; this + * can lead to ClassCastExceptions if sending through the untyped ActorRef + */ +trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { + + import macros.Helpers._ + + /** + * Create a child actor with properly typed ChannelRef, verifying that this + * actor can handle everything which the child tries to send via its + * `parent` ChannelRef. + */ + def createChild[Pa <: ChannelList, Ch <: ChannelList](factory: Channels[Pa, Ch]): ChannelRef[Ch] = macro macros.CreateChild.impl[C, Pa, Ch] + + /** + * Properly typed ChannelRef for the context.parent. + */ + final def parentChannel: ChannelRef[P] = new ChannelRef(context.parent) + + /** + * The properly typed self-channel is used implicitly when sending to other + * typed channels for verifying that replies can be handled. + */ + implicit final def selfChannel = new ChannelRef[C](self) + + /* + * This map holds the current behavior for each erasure-tagged channel; the + * basic receive impl will dispatch incoming messages according to the most + * specific erased type in this map. + */ + private var behavior = Map.empty[Class[_], FF] + + /** + * Functions for storage in the behavior, to get around erasure + */ + private trait FF + private case class F1(f: (WrappedMessage[ChannelList], ChannelRef[ChannelList]) ⇒ Unit) extends FF + private case class F2(f: (Any, ChannelRef[ChannelList]) ⇒ Unit) extends FF + + /** + * Declare an input channel of the given type; the returned object takes a partial function: + * + * {{{ + * channel[A] { + * case (a, s) => + * // a is of type A and + * // s is a ChannelRef for the sender, capable of sending the declared reply type for A + * } + * }}} + */ + def channel[T]: Channels[P, C]#Behaviorist[Nothing, T] = macro macros.Channel.impl[T, C, P] + + class Behaviorist[-R, Ch](tt: ru.TypeTag[Ch], wrapped: Boolean) { + private def ff(recv: R): FF = + if (wrapped) + F1(recv.asInstanceOf[(WrappedMessage[ChannelList], ChannelRef[ChannelList]) ⇒ Unit]) + else + F2(recv.asInstanceOf[(Any, ChannelRef[ChannelList]) ⇒ Unit]) + def apply(recv: R): Unit = + behavior ++= (for (t ← inputChannels(ru)(tt.tpe)) yield tt.mirror.runtimeClass(t.widen) -> ff(recv)) + } + + /* + * HORRIBLE HACK AHEAD + * + * I’d like to keep this a trait, but traits cannot have constructor + * arguments, not even TypeTags. + */ + protected var channelListTypeTag: TypeTag[C] = _ + + /** + * Sort so that subtypes always precede their supertypes, but without + * obeying any order between unrelated subtypes (insert sort). + */ + private def sortClasses(in: Iterable[Class[_]]): immutable.Seq[Class[_]] = + (new ArrayBuffer[Class[_]](in.size) /: in) { (buf, cls) ⇒ + buf.indexWhere(_ isAssignableFrom cls) match { + case -1 ⇒ buf append cls + case x ⇒ buf insert (x, cls) + } + buf + }.to[immutable.IndexedSeq] + + final lazy val receive = new AbstractPartialFunction[Any, Unit] { + + val index = sortClasses(behavior.keys) + + if (channelListTypeTag != null) verifyCompleteness() + + private def verifyCompleteness() { + val channels = inputChannels(ru)(channelListTypeTag.tpe) + val classes = channels groupBy (e ⇒ channelListTypeTag.mirror.runtimeClass(e.widen)) + val missing = classes.keySet -- behavior.keySet + if (missing.nonEmpty) { + val m = missing.map(classes).flatten + throw ActorInitializationException(s"missing declarations for channels ${m mkString ", "}") + } + } + + override def applyOrElse[A, B >: Unit](x: A, default: A ⇒ B): B = x match { + case CheckType(tt) ⇒ + narrowCheck(ru)(channelListTypeTag.tpe, tt.tpe) match { + case Nil ⇒ sender ! CheckTypeACK + case err :: Nil ⇒ sender ! CheckTypeNAK(err) + case list ⇒ sender ! CheckTypeNAK(list mkString ("multiple errors:\n - ", " - ", "")) + } + case _ ⇒ + val msgClass = x.getClass + index find (_ isAssignableFrom msgClass) match { + case None ⇒ default(x) + case Some(cls) ⇒ + behavior(cls) match { + case F1(f) ⇒ f(new WrappedMessage[ChannelList](x), new ChannelRef(sender)) + case F2(f) ⇒ f(x, new ChannelRef(sender)) + } + } + } + + def isDefinedAt(x: Any): Boolean = x match { + case c: CheckType[_] ⇒ true + case _ ⇒ + val msgClass = x.getClass + index find (_ isAssignableFrom msgClass) match { + case None ⇒ false + case Some(cls) ⇒ true + } + } + } +} + +object Channels { + +} \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala new file mode 100644 index 0000000000..21ad45eec9 --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -0,0 +1,36 @@ +package akka.channels + +import language.experimental.{ macros ⇒ makkros } +import akka.actor.ActorRef +import akka.util.Timeout +import akka.pattern.ask +import scala.concurrent.{ ExecutionContext, Future } +import scala.reflect.runtime.{ universe ⇒ ru } +import scala.util.Success + +sealed trait ChannelList +sealed trait TNil extends ChannelList +sealed trait :+:[A <: (_, _), B <: ChannelList] extends ChannelList + +class ActorRefOps(val ref: ActorRef) extends AnyVal { + import macros.Helpers._ + def narrow[C <: ChannelList](implicit timeout: Timeout, ec: ExecutionContext, tt: ru.TypeTag[C]): Future[ChannelRef[C]] = { + import Channels._ + ref ? CheckType(tt) map { + case CheckTypeACK ⇒ new ChannelRef[C](ref) + case CheckTypeNAK(error) ⇒ throw NarrowingException(error) + } + } +} + +class FutureOps[T](val future: Future[T]) extends AnyVal { + def -!->[C <: ChannelList](channel: ChannelRef[C]): Future[T] = macro macros.Tell.futureImpl[C, T] + def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.futureImpl[C, T] +} + +class AnyOps[T](val value: T) extends AnyVal { + def -!->[C <: ChannelList](channel: ChannelRef[C]): Unit = macro macros.Tell.opsImpl[C, T] + def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.opsImpl[C, T] +} + +class WrappedMessage[T <: ChannelList](val value: Any) extends AnyVal diff --git a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala new file mode 100644 index 0000000000..2c31f65ae4 --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala @@ -0,0 +1,63 @@ +package akka.channels.macros + +import akka.channels._ +import scala.concurrent.Future +import akka.util.Timeout +import scala.reflect.runtime.{ universe ⇒ ru } +import ru.TypeTag +import scala.reflect.macros.Context +import scala.reflect.api.Universe +import akka.actor.ActorRef +import akka.dispatch.ExecutionContexts + +object Ask { + import Helpers._ + + def impl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[T] + })(msg: c.Expr[M]): c.Expr[Future[_]] = { + import c.universe._ + askTree(c)(weakTypeOf[M], weakTypeOf[T])(reify(c.prefix.splice.actorRef), msg) + } + + def opsImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = AnyOps[M] + })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[_]] = { + import c.universe._ + askTree(c)(weakTypeOf[M], weakTypeOf[T])(reify(channel.splice.actorRef), reify(c.prefix.splice.value)) + } + + def futureImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = FutureOps[M] + })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[_]] = { + import c.universe._ + val tree = askTree(c)(weakTypeOf[M], weakTypeOf[T])(c.Expr(Ident("c$1")), c.Expr(Ident("x$1"))) + reify({ + val c$1 = channel.splice.actorRef + c.prefix.splice.future.flatMap(x$1 ⇒ tree.splice)(ExecutionContexts.sameThreadExecutionContext) + }) + } + + def askTree[M](c: Context with Singleton)(msgT: c.universe.Type, chT: c.universe.Type)(target: c.Expr[ActorRef], msg: c.Expr[M]): c.Expr[Future[_]] = { + import c.universe._ + val out = replyChannels(c.universe)(chT, msgT) + if (out.isEmpty) { + c.error(c.enclosingPosition, s"This ChannelRef does not support messages of type $msgT") + reify(null) + } else { + val timeout = c.inferImplicitValue(typeOf[Timeout]) + if (timeout.isEmpty) + c.error(c.enclosingPosition, s"no implicit akka.util.Timeout found") + val result = appliedType(weakTypeOf[Future[_]].typeConstructor, List(lub(out))) + c.Expr( + TypeApply( + Select( + reify(akka.pattern.ask( + target.splice, msg.splice)( + c.Expr(timeout)(weakTypeTag[Timeout]).splice)).tree, + "asInstanceOf"), + List(TypeTree().setType(result))))(weakTypeTag[Future[_]]) + } + } + +} \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala new file mode 100644 index 0000000000..f5234778de --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala @@ -0,0 +1,77 @@ +package akka.channels.macros + +import akka.channels._ +import scala.reflect.runtime.{ universe ⇒ ru } +import ru.TypeTag +import scala.reflect.macros.Context +import scala.reflect.api.Universe + +object Channel { + import Helpers._ + + /** + * This macro transforms a channel[] call which returns “some” Behaviorist + * into a _channel[] call with precise reply channel descriptors, so that the + * partial function it is applied to can enjoy proper type checking. + * + * T is the message type + * C is the channel list of the enclosing Channels + * P is the parent channel list + */ + def impl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: ChannelList: c.WeakTypeTag]( + c: Context { + type PrefixType = Channels[P, C] + }): c.Expr[Channels[P, C]#Behaviorist[Nothing, T]] = { + + val tT = c.weakTypeOf[T] + val tC = c.weakTypeOf[C] + + import c.universe._ + + val undefined = missingChannels(c.universe)(tC, inputChannels(c.universe)(tT)) + if (undefined.nonEmpty) { + c.error(c.enclosingPosition, s"no channel defined for types ${undefined mkString ", "}") + reify(null) + } else { + checkUnique(c.universe)(tT, tC) foreach (c.error(c.enclosingPosition, _)) + val channels = toChannels(c.universe)(replyChannels(c.universe)(tC, tT)) + val (receive, wrapped) = + if (tT <:< typeOf[ChannelList]) { + appliedType(typeOf[Function2[_, _, _]].typeConstructor, List( + appliedType(typeOf[WrappedMessage[_]].typeConstructor, List(tT)), + appliedType(typeOf[ChannelRef[_]].typeConstructor, List(channels)), + typeOf[Unit])) -> true + } else { + appliedType(typeOf[Function2[_, _, _]].typeConstructor, List( + tT, + appliedType(typeOf[ChannelRef[_]].typeConstructor, List(channels)), + typeOf[Unit])) -> false + } + c.Expr( + Block(List( + If( + { + val cltt = c.Expr(Select(c.prefix.tree, "channelListTypeTag")) + reify(cltt.splice == null).tree + }, + Apply( + Select(c.prefix.tree, "channelListTypeTag_$eq"), + List(TypeApply( + Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), + List(TypeTree().setType(c.weakTypeOf[C]))))), + c.literalUnit.tree)), + Apply( + Select( + New(AppliedTypeTree(Select(c.prefix.tree, newTypeName("Behaviorist")), List( + TypeTree().setType(receive), + TypeTree().setType(tT)))), + nme.CONSTRUCTOR), + List( + TypeApply( + Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), + List(TypeTree().setType(tT))), + Literal(Constant(wrapped)))))) + } + } + +} \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala new file mode 100644 index 0000000000..94b3ba8abc --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala @@ -0,0 +1,35 @@ +package akka.channels.macros + +import akka.channels._ +import scala.reflect.runtime.{ universe ⇒ ru } +import ru.TypeTag +import scala.reflect.macros.Context +import scala.reflect.api.Universe +import akka.actor.Props + +object CreateChild { + import Helpers._ + + def impl[C <: ChannelList: c.WeakTypeTag, Pa <: ChannelList: c.WeakTypeTag, Ch <: ChannelList: c.WeakTypeTag]( + c: Context { + type PrefixType = Channels[_, C] + })(factory: c.Expr[Channels[Pa, Ch]]): c.Expr[ChannelRef[Ch]] = { + + import c.universe._ + if (weakTypeOf[Pa] =:= weakTypeOf[Nothing]) { + c.abort(c.enclosingPosition, "Parent argument must not be Nothing") + } + if (weakTypeOf[Ch] =:= weakTypeOf[Nothing]) { + c.abort(c.enclosingPosition, "channel list must not be Nothing") + } + val missing = missingChannels(c.universe)(weakTypeOf[C], inputChannels(c.universe)(weakTypeOf[Pa])) + if (missing.isEmpty) { + implicit val t = c.TypeTag[Ch](c.weakTypeOf[Ch]) + reify(new ChannelRef[Ch](c.prefix.splice.context.actorOf(Props(factory.splice)))) + } else { + c.error(c.enclosingPosition, s"This actor cannot support a child requiring channels ${missing mkString ", "}") + reify(???) + } + } + +} \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala new file mode 100644 index 0000000000..c72bd53392 --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -0,0 +1,119 @@ +package akka.channels.macros + +import akka.AkkaException +import scala.util.control.NoStackTrace +import akka.channels._ +import scala.reflect.runtime.{ universe ⇒ ru } +import ru.TypeTag +import scala.reflect.macros.Context +import scala.reflect.api.Universe + +object Helpers { + + type Recv[T, Ch <: ChannelList] = Function2[T, ChannelRef[Ch], Unit] + + case class CheckType[T](tt: TypeTag[T]) + case object CheckTypeACK + case class CheckTypeNAK(errors: String) + + def error(c: Context, msg: String) = c.error(c.enclosingPosition, msg) + def abort(c: Context, msg: String) = c.abort(c.enclosingPosition, msg) + + def checkUnique(u: Universe)(channel: u.Type, list: u.Type): Option[String] = { + val channels = inputChannels(u)(list) groupBy (_.erasure) + val dupes = channels.get(channel.erasure).getOrElse(Nil).filterNot(_ =:= channel) + if (dupes.isEmpty) None + else Some(s"erasure ${channel.erasure} overlaps with declared channels ${dupes mkString ", "}") + } + + /** + * check that the original ChannelList is a subtype of the target ChannelList; return a list or error strings + */ + def narrowCheck(u: Universe)(orig: u.Type, target: u.Type): List[String] = { + var errors = List.empty[String] + for (in ← inputChannels(u)(target)) { + val replies = replyChannels(u)(orig, in) + if (replies.isEmpty) errors ::= s"original ChannelRef does not support input type $in" + else { + val targetReplies = replyChannels(u)(target, in) + val unsatisfied = replies filterNot (r ⇒ targetReplies exists (r <:< _)) + if (unsatisfied.nonEmpty) errors ::= s"reply types ${unsatisfied mkString ", "} not covered for channel $in" + val leftovers = targetReplies filterNot (t ⇒ replies exists (_ <:< t)) + if (leftovers.nonEmpty) errors ::= s"desired reply types ${leftovers mkString ", "} are superfluous for channel $in" + } + } + errors.reverse + } + + /** + * get all input channels from a ChannelList or return the given type + */ + final def inputChannels(u: Universe)(list: u.Type): List[u.Type] = { + import u._ + val imp = u.mkImporter(ru) + val cl = imp.importType(ru.typeOf[ChannelList]) + val tnil = imp.importType(ru.typeOf[TNil]) + def rec(l: u.Type, acc: List[u.Type]): List[u.Type] = l match { + case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, if (acc contains in) acc else in :: acc) + case last ⇒ if (last =:= tnil) acc.reverse else (last :: acc).reverse + } + if (list <:< cl) rec(list, Nil) + else List(list) + } + + /** + * find all input channels matching the given message type and return a + * list of their respective reply channels + */ + final def replyChannels(u: Universe)(list: u.Type, msg: u.Type): List[u.Type] = { + import u._ + def rec(l: Type, acc: List[Type]): List[Type] = { + l match { + case TypeRef(_, _, TypeRef(_, _, in :: out :: Nil) :: tail :: Nil) if msg <:< in ⇒ + rec(tail, if (acc contains out) acc else out :: acc) + case TypeRef(_, _, _ :: tail :: Nil) ⇒ + rec(tail, acc) + case _ ⇒ acc.reverse + } + } + val n = typeOf[Nothing] + if (msg =:= n) List(n) else rec(list, Nil) + } + + /** + * filter from the `required` list of types all which are subtypes of inputs of the ChannelList + */ + 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 (when compiling the macro itself) + 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) + } + + /** + * convert a list of types List(, , ...) into a ChannelList + * ( Channel[, Nothing] :=: Channel[, Nothing] :=: ... :=: TNil ) + */ + final def toChannels(u: Universe)(list: List[u.Type]): u.Type = { + import u._ + def rec(l: List[Type], acc: Type): Type = l match { + case head :: (tail: List[Type]) ⇒ + if (head =:= weakTypeOf[Nothing]) rec(tail, acc) + else + rec(tail, + appliedType(weakTypeOf[:+:[_, _]].typeConstructor, List( + appliedType(weakTypeOf[Tuple2[_, _]].typeConstructor, List( + head, + weakTypeOf[Nothing])), + acc))) + case _ ⇒ acc + } + rec(list.reverse, weakTypeOf[TNil]) + } + +} \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala b/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala new file mode 100644 index 0000000000..2f9f6d3db5 --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala @@ -0,0 +1,24 @@ +package akka.channels.macros + +import akka.channels._ +import scala.reflect.runtime.{ universe ⇒ ru } +import ru.TypeTag +import scala.reflect.macros.Context +import scala.reflect.api.Universe + +object Narrow { + import Helpers._ + + def impl[C <: ChannelList: c.WeakTypeTag, T <: ChannelList: c.WeakTypeTag]( + c: Context { + type PrefixType = ChannelRef[T] + }): c.Expr[ChannelRef[C]] = { + import c.{ universe ⇒ u } + narrowCheck(u)(u.weakTypeOf[T], u.weakTypeOf[C]) match { + case Nil ⇒ // okay + case err :: Nil ⇒ c.error(c.enclosingPosition, err) + case list ⇒ c.error(c.enclosingPosition, list mkString ("multiple errors:\n - ", "\n - ", "")) + } + u.reify(c.prefix.splice.asInstanceOf[ChannelRef[C]]) + } +} \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala new file mode 100644 index 0000000000..ce0d56b134 --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala @@ -0,0 +1,90 @@ +package akka.channels.macros + +import akka.channels._ +import akka.actor._ +import scala.reflect.runtime.{ universe ⇒ ru } +import ru.TypeTag +import scala.reflect.macros.Context +import scala.reflect.api.Universe +import scala.annotation.tailrec +import scala.concurrent.Future +import scala.util.{ Failure, Success } +import akka.dispatch.ExecutionContexts + +object Tell { + import Helpers._ + + def impl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[T] + })(msg: c.Expr[M]): c.Expr[Unit] = { + val tT = c.universe.weakTypeOf[T] + val (tS, senderTree, sender) = getSenderChannel(c) + + verify(c)(senderTree, c.universe.weakTypeOf[M], tS, tT) + + c.universe.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice)) + } + + def opsImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = AnyOps[M] + })(channel: c.Expr[ChannelRef[T]]): c.Expr[Unit] = { + val tT = c.universe.weakTypeOf[T] + val (tS, senderTree, sender) = getSenderChannel(c) + + verify(c)(senderTree, c.universe.weakTypeOf[M], tS, tT) + + c.universe.reify(channel.splice.actorRef.tell(c.prefix.splice.value, sender.splice)) + } + + def futureImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = FutureOps[M] + })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[M]] = { + val tT = c.universe.weakTypeOf[T] + val (tS, senderTree, sender) = getSenderChannel(c) + + verify(c)(senderTree, c.universe.weakTypeOf[M], tS, tT) + + c.universe.reify( + { + val s$1 = sender.splice + c.prefix.splice.future.andThen { + case Success(s) ⇒ channel.splice.actorRef.tell(s, s$1) + case _ ⇒ + }(ExecutionContexts.sameThreadExecutionContext) + }) + } + + def getSenderChannel(c: Context): (c.universe.Type, c.Tree, c.Expr[ActorRef]) = { + val replyChannel = c.inferImplicitValue(c.typeOf[ChannelRef[_]]) + if (!replyChannel.isEmpty) { + import c.universe._ + replyChannel.tpe match { + case TypeRef(_, _, param :: Nil) ⇒ + (param, replyChannel, c.Expr(Select(replyChannel, "actorRef"))(c.universe.weakTypeTag[ActorRef])) + } + } else abort(c, "no implicit sender ChannelRef found") + } + + def verify(c: Context)(sender: c.universe.Tree, msgT: c.universe.Type, sndT: c.universe.Type, chT: c.universe.Type)(): Unit = { + def rec(msg: Set[c.universe.Type], checked: Set[c.universe.Type], depth: Int): Unit = + if (msg.nonEmpty) { + val u: c.universe.type = c.universe + val replies = msg map (m ⇒ m -> replyChannels(u)(chT, m)) + val missing = replies collect { case (k, v) if v.size == 0 ⇒ k } + if (missing.nonEmpty) + error(c, s"target ChannelRef does not support messages of types ${missing mkString ", "} (at depth $depth)") + else { + val nextSend = replies.map(_._2).flatten map (m ⇒ m -> replyChannels(u)(sndT, m)) + val nextMissing = nextSend collect { case (k, v) if v.size == 0 ⇒ k } + if (nextMissing.nonEmpty) + error(c, s"implicit sender `$sender` does not support messages of the reply types ${nextMissing mkString ", "} (at depth $depth)") + else { + val nextChecked = checked ++ msg + val nextMsg = nextSend.map(_._2).flatten -- nextChecked + rec(nextMsg, nextChecked, depth + 1) + } + } + } + rec(Set(msgT), Set(c.universe.typeOf[Nothing]), 1) + } +} \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/package.scala b/akka-channels/src/main/scala/akka/channels/package.scala new file mode 100644 index 0000000000..0b0a53de24 --- /dev/null +++ b/akka-channels/src/main/scala/akka/channels/package.scala @@ -0,0 +1,15 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ + +package akka + +import language.implicitConversions +import akka.actor.ActorRef +import scala.concurrent.Future + +package object channels { + implicit def actorRefOps(ref: ActorRef) = new ActorRefOps(ref) + implicit def futureOps[T](f: Future[T]) = new FutureOps(f) + implicit def anyOps[T](x: T) = new AnyOps(x) +} diff --git a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala b/akka-macros/src/main/scala/akka/channels/ChannelRef.scala deleted file mode 100644 index aa78dc0c04..0000000000 --- a/akka-macros/src/main/scala/akka/channels/ChannelRef.scala +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (C) 2009-2012 Typesafe Inc. - */ - -package akka.channels - -import language.experimental.macros -import akka.actor.ActorRef -import scala.reflect.runtime.universe.TypeTag -import scala.reflect.macros.Context -import scala.annotation.tailrec -import scala.reflect.macros.Universe -import akka.actor.Actor -import akka.actor.ActorContext -import scala.concurrent.Future -import akka.util.Timeout - -class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { - - def ![M](msg: M): Unit = macro ChannelRef.tell[T, M] - - def ?[M](msg: M): Future[_] = macro ChannelRef.ask[T, M] - - def forward[M](msg: M): Unit = macro ChannelRef.forward[T, M] - - def narrow[C <: ChannelList]: ChannelRef[C] = macro ChannelRef.narrowImpl[C, T] - -} - -object ChannelRef { - import Channels._ - - def tell[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = ChannelRef[T] - })(msg: c.Expr[M]): c.Expr[Unit] = { - import c.{ universe ⇒ u } - - def getSenderChannel = { - val replyChannel = c.inferImplicitValue(c.typeOf[ChannelRef[_]]) - if (!replyChannel.isEmpty) { - import u._ - replyChannel.tpe match { - case TypeRef(_, _, param :: Nil) ⇒ - Some((param, replyChannel, c.Expr(Select(replyChannel, "actorRef"))(u.weakTypeTag[ActorRef]))) - } - } else None - } - - def getSenderRef = { - val senderTree = c.inferImplicitValue(c.typeOf[ActorRef]) - if (senderTree.isEmpty) { - val noSender = c.universe.reify(Actor.noSender) - ((u.typeOf[(Any, Nothing) :+: TNil], noSender.tree, noSender)) - } else ((u.typeOf[(Any, Any) :+: TNil], senderTree, c.Expr(senderTree)(c.WeakTypeTag(senderTree.tpe)))) - } - - val tT = u.weakTypeOf[T] - val (tS, senderTree, sender) = getSenderChannel getOrElse getSenderRef - - def err(msg: String) = c.error(c.enclosingPosition, msg) - - def verify(msg: Set[u.Type], checked: Set[u.Type], depth: Int): Unit = if (msg.nonEmpty) { - val replies = msg map (m ⇒ m -> replyChannels(u)(tT, m)) - val missing = replies collect { case (k, v) if v.size == 0 ⇒ k } - if (missing.nonEmpty) - err(s"target ChannelRef does not support messages of types ${missing mkString ", "} (at depth $depth)") - else { - val nextSend = replies.map(_._2).flatten map (m ⇒ m -> replyChannels(u)(tS, m)) - val nextMissing = nextSend collect { case (k, v) if v.size == 0 ⇒ k } - if (nextMissing.nonEmpty) - err(s"implicit sender `$senderTree` does not support messages of the reply types ${nextMissing mkString ", "} (at depth $depth)") - else { - val nextChecked = checked ++ msg - val nextMsg = nextSend.map(_._2).flatten -- nextChecked - verify(nextMsg, nextChecked, depth + 1) - } - } - } - - verify(Set(u.weakTypeOf[M]), Set(u.typeOf[Nothing]), 1) - u.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice)) - } - - def ask[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = ChannelRef[T] - })(msg: c.Expr[M]): c.Expr[Future[_]] = { - import c.universe._ - - val out = replyChannels(c.universe)(weakTypeOf[T], weakTypeOf[M]) - if (out.isEmpty) { - c.error(c.enclosingPosition, s"This ChannelRef does not support messages of type ${weakTypeOf[M]}") - reify(null) - } else { - val timeout = c.inferImplicitValue(typeOf[Timeout]) - if (timeout.isEmpty) - c.error(c.enclosingPosition, s"no implicit akka.util.Timeout found") - val result = appliedType(weakTypeOf[Future[_]].typeConstructor, List(lub(out))) - c.Expr( - TypeApply( - Select( - reify(akka.pattern.ask( - c.prefix.splice.actorRef, msg.splice)( - c.Expr(timeout)(weakTypeTag[Timeout]).splice)).tree, - "asInstanceOf"), - List(TypeTree().setType(result)))) - } - } - - def forward[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = ChannelRef[T] - })(msg: c.Expr[M]): c.Expr[Unit] = { - import c.universe._ - if (weakTypeOf[M] =:= weakTypeOf[Values.WrappedMessage[T]]) { - reify( - c.prefix.splice.actorRef.forward( - msg.splice.asInstanceOf[Values.WrappedMessage[_]].value)( - c.Expr(Ident("implicitly"))(weakTypeTag[ActorContext]).splice)) - } else { - c.error(c.enclosingPosition, s"cannot forward message unless types match exactly") - reify(()) - } - } - - def narrowImpl[C <: ChannelList: c.WeakTypeTag, T <: ChannelList: c.WeakTypeTag]( - c: Context { - type PrefixType = ChannelRef[T] - }): c.Expr[ChannelRef[C]] = { - import c.{ universe ⇒ u } - narrowCheck(u)(u.weakTypeOf[T], u.weakTypeOf[C]) match { - case Nil ⇒ // okay - case err :: Nil ⇒ c.error(c.enclosingPosition, err) - case list ⇒ c.error(c.enclosingPosition, list mkString ("multiple errors:\n - ", "\n - ", "")) - } - u.reify(c.prefix.splice.asInstanceOf[ChannelRef[C]]) - } - -} \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/Channels.scala b/akka-macros/src/main/scala/akka/channels/Channels.scala deleted file mode 100644 index 5c3b979ae3..0000000000 --- a/akka-macros/src/main/scala/akka/channels/Channels.scala +++ /dev/null @@ -1,357 +0,0 @@ -/** - * Copyright (C) 2009-2012 Typesafe Inc. - */ - -package akka.channels - -import language.experimental.macros -import akka.actor.{ Actor, ActorRef } -import scala.reflect.macros.Context -import scala.reflect.runtime.{ universe ⇒ ru } -import scala.reflect.runtime.universe.TypeTag -import scala.reflect.api.Universe -import scala.runtime.AbstractPartialFunction -import akka.actor.Props -import scala.collection.immutable -import scala.collection.mutable.ArrayBuffer -import scala.reflect.{ classTag, ClassTag } -import scala.concurrent.{ ExecutionContext, Future } -import akka.util.Timeout -import akka.pattern.ask -import scala.util.control.NoStackTrace -import akka.AkkaException -import akka.actor.ExtendedActorSystem -import akka.actor.ActorInitializationException - -/** - * Typed channels atop untyped actors. - * - * The idea is that the actor declares all its input types up front, including - * what it expects the sender to handle wrt.replies, and then ChannelRef - * carries this information for statically verifying that messages sent to an - * actor have an actual chance of being processed. - * - * There are several implementation-imposed restrictions: - * - * - not two channels with different input types may have the same erased - * type; this is currently not enforced at compile time (and leads to - * channels being “ignored” at runtime) - * - messages received by the actor are dispatched to channels based on the - * erased type, which may be less precise than the actual channel type; this - * can lead to ClassCastExceptions if sending through the untyped ActorRef - */ -trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { - - import Channels._ - - /** - * Create a child actor with properly typed ChannelRef, verifying that this - * actor can handle everything which the child tries to send via its - * `parent` ChannelRef. - */ - def createChild[Pa <: ChannelList, Ch <: ChannelList](factory: Channels[Pa, Ch]): ChannelRef[Ch] = macro createChildImpl[C, Pa, Ch] - - /** - * Properly typed ChannelRef for the context.parent. - */ - def parentChannel: ChannelRef[P] = new ChannelRef(context.parent) - - /** - * The properly typed self-channel is used implicitly when sending to other - * typed channels for verifying that replies can be handled. - */ - implicit def selfChannel = new ChannelRef[C](self) - - /* - * This map holds the current behavior for each erasure-tagged channel; the - * basic receive impl will dispatch incoming messages according to the most - * specific erased type in this map. - */ - private var behavior = Map.empty[Class[_], FF] - - private trait FF - private object FF { - def apply(x: Any): FF = x match { - case f: Function1[_, _] ⇒ F1(f.asInstanceOf[Values.WrappedMessage[ChannelList] ⇒ Unit]) - case f: Function2[_, _, _] ⇒ F2(f.asInstanceOf[(Any, ChannelRef[ChannelList]) ⇒ Unit]) - } - } - private case class F1(f: Values.WrappedMessage[ChannelList] ⇒ Unit) extends FF - private case class F2(f: (Any, ChannelRef[ChannelList]) ⇒ Unit) extends FF - - /** - * Declare an input channel of the given type; the returned object takes a partial function: - * - * {{{ - * channel[A] { - * case (a, s) => - * // a is of type A and - * // s is a ChannelRef for the sender, capable of sending the declared reply type for A - * } - * }}} - */ - def channel[T]: Channels[P, C]#Behaviorist[Nothing, T] = macro channelImpl[T, C, P] - - new Behaviorist(null) - - protected class Behaviorist[-R, Ch](tt: ru.TypeTag[Ch]) { - def apply(recv: R): Unit = - behavior ++= (for (t ← inputChannels(ru)(tt.tpe)) yield tt.mirror.runtimeClass(t.widen) -> FF(recv)) - } - - /* - * HORRIBLE HACK AHEAD - * - * I’d like to keep this a trait, but traits cannot have constructor - * arguments, not even TypeTags. - */ - protected var channelListTypeTag: TypeTag[C] = _ - - /** - * Sort so that subtypes always precede their supertypes, but without - * obeying any order between unrelated subtypes (insert sort). - */ - private def sortClasses(in: Iterable[Class[_]]): immutable.Seq[Class[_]] = - (new ArrayBuffer[Class[_]](in.size) /: in) { (buf, cls) ⇒ - buf.indexWhere(_ isAssignableFrom cls) match { - case -1 ⇒ buf append cls - case x ⇒ buf insert (x, cls) - } - buf - }.to[immutable.IndexedSeq] - - final lazy val receive = new AbstractPartialFunction[Any, Unit] { - - val index = sortClasses(behavior.keys) - - if (channelListTypeTag != null) verifyCompleteness() - - private def verifyCompleteness() { - val channels = inputChannels(ru)(channelListTypeTag.tpe) - val classes = channels groupBy (e ⇒ channelListTypeTag.mirror.runtimeClass(e.widen)) - val missing = classes.keySet -- behavior.keySet - if (missing.nonEmpty) { - val m = missing.map(classes).flatten - throw ActorInitializationException(s"missing declarations for channels ${m mkString ", "}") - } - } - - override def applyOrElse[A, B >: Unit](x: A, default: A ⇒ B): B = x match { - case CheckType(tt) ⇒ - narrowCheck(ru)(channelListTypeTag.tpe, tt.tpe) match { - case Nil ⇒ sender ! CheckTypeACK - case err :: Nil ⇒ sender ! CheckTypeNAK(err) - case list ⇒ sender ! CheckTypeNAK(list mkString ("multiple errors:\n - ", " - ", "")) - } - case _ ⇒ - val msgClass = x.getClass - index find (_ isAssignableFrom msgClass) match { - case None ⇒ default(x) - case Some(cls) ⇒ - behavior(cls) match { - case F1(f) ⇒ f(new Values.WrappedMessage[ChannelList](x)) - case F2(f) ⇒ f(x, new ChannelRef(sender)) - } - } - } - - def isDefinedAt(x: Any): Boolean = x match { - case c: CheckType[_] ⇒ true - case _ ⇒ - val msgClass = x.getClass - index find (_ isAssignableFrom msgClass) match { - case None ⇒ false - case Some(cls) ⇒ true - } - } - } -} - -object Channels { - - type Recv[T, Ch <: ChannelList] = Function2[T, ChannelRef[Ch], Unit] - - case class CheckType[T](tt: TypeTag[T]) - case object CheckTypeACK - case class CheckTypeNAK(errors: String) - case class NarrowingException(errors: String) extends AkkaException(errors) with NoStackTrace - - /** - * This macro transforms a channel[] call which returns “some” Behaviorist - * into a _channel[] call with precise reply channel descriptors, so that the - * partial function it is applied to can enjoy proper type checking. - * - * T is the message type - * C is the channel list of the enclosing Channels - * P is the parent channel list - */ - def channelImpl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: ChannelList: c.WeakTypeTag]( - c: Context { - type PrefixType = Channels[P, C] - }): c.Expr[Channels[P, C]#Behaviorist[Nothing, T]] = { - - val tT = c.weakTypeOf[T] - val tC = c.weakTypeOf[C] - - import c.universe._ - - val undefined = missingChannels(c.universe)(tC, inputChannels(c.universe)(tT)) - if (undefined.nonEmpty) { - c.error(c.enclosingPosition, s"no channel defined for types ${undefined mkString ", "}") - reify(null) - } else { - checkUnique(c.universe)(tT, tC) foreach (c.error(c.enclosingPosition, _)) - val receive = - if (tT <:< typeOf[ChannelList]) { - appliedType(typeOf[Function1[_, _]].typeConstructor, List( - appliedType(typeOf[Values.WrappedMessage[_]].typeConstructor, List(tT)), - typeOf[Unit])) - } else { - val channels = toChannels(c.universe)(replyChannels(c.universe)(tC, tT)) - appliedType(typeOf[Function2[_, _, _]].typeConstructor, List( - tT, - appliedType(typeOf[ChannelRef[_]].typeConstructor, List(channels)), - typeOf[Unit])) - } - c.Expr( - Apply( - Select( - New(AppliedTypeTree(Select(c.prefix.tree, newTypeName("Behaviorist")), List( - TypeTree().setType(receive), - TypeTree().setType(tT)))), - nme.CONSTRUCTOR), - List( - Block(List( - If(reify(c.prefix.splice.channelListTypeTag == null).tree, - Apply( - Select(c.prefix.tree, "channelListTypeTag_$eq"), - List(TypeApply( - Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), - List(TypeTree().setType(c.weakTypeOf[C]))))), - c.literalUnit.tree)), - TypeApply( - Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), - List(TypeTree().setType(tT))))))) - } - } - - def createChildImpl[C <: ChannelList: c.WeakTypeTag, Pa <: ChannelList: c.WeakTypeTag, Ch <: ChannelList: c.WeakTypeTag]( - c: Context { - type PrefixType = Channels[_, C] - })(factory: c.Expr[Channels[Pa, Ch]]): c.Expr[ChannelRef[Ch]] = { - - import c.universe._ - if (weakTypeOf[Pa] =:= weakTypeOf[Nothing]) { - c.abort(c.enclosingPosition, "Parent argument must not be Nothing") - } - if (weakTypeOf[Ch] =:= weakTypeOf[Nothing]) { - c.abort(c.enclosingPosition, "channel list must not be Nothing") - } - val missing = missingChannels(c.universe)(weakTypeOf[C], inputChannels(c.universe)(weakTypeOf[Pa])) - if (missing.isEmpty) { - implicit val t = c.TypeTag[Ch](c.weakTypeOf[Ch]) - reify(new ChannelRef[Ch](c.prefix.splice.context.actorOf(Props(factory.splice)))) - } else { - c.error(c.enclosingPosition, s"This actor cannot support a child requiring channels ${missing mkString ", "}") - reify(???) - } - } - - def checkUnique(u: Universe)(channel: u.Type, list: u.Type): Option[String] = { - val channels = inputChannels(u)(list) groupBy (_.erasure) - val dupes = channels.get(channel.erasure).getOrElse(Nil).filterNot(_ =:= channel) - if (dupes.isEmpty) None - else Some(s"erasure ${channel.erasure} overlaps with declared channels ${dupes mkString ", "}") - } - - /** - * check that the original ChannelList is a subtype of the target ChannelList; return a list or error strings - */ - def narrowCheck(u: Universe)(orig: u.Type, target: u.Type): List[String] = { - var errors = List.empty[String] - for (in ← inputChannels(u)(target)) { - val replies = replyChannels(u)(orig, in) - if (replies.isEmpty) errors ::= s"original ChannelRef does not support input type $in" - else { - val targetReplies = replyChannels(u)(target, in) - val unsatisfied = replies filterNot (r ⇒ targetReplies exists (r <:< _)) - if (unsatisfied.nonEmpty) errors ::= s"reply types ${unsatisfied mkString ", "} not covered for channel $in" - val leftovers = targetReplies filterNot (t ⇒ replies exists (_ <:< t)) - if (leftovers.nonEmpty) errors ::= s"desired reply types ${leftovers mkString ", "} are superfluous for channel $in" - } - } - errors.reverse - } - - /** - * get all input channels from a ChannelList or return the given type - */ - final def inputChannels(u: Universe)(list: u.Type): List[u.Type] = { - import u._ - val imp = u.mkImporter(ru) - val cl = imp.importType(ru.typeOf[ChannelList]) - val tnil = imp.importType(ru.typeOf[TNil]) - def rec(l: u.Type, acc: List[u.Type]): List[u.Type] = l match { - case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, if (acc contains in) acc else in :: acc) - case last ⇒ if (last =:= tnil) acc.reverse else (last :: acc).reverse - } - if (list <:< cl) rec(list, Nil) - else List(list) - } - - /** - * find all input channels matching the given message type and return a - * list of their respective reply channels - */ - final def replyChannels(u: Universe)(list: u.Type, msg: u.Type): List[u.Type] = { - import u._ - def rec(l: Type, acc: List[Type]): List[Type] = { - l match { - case TypeRef(_, _, TypeRef(_, _, in :: out :: Nil) :: tail :: Nil) if msg <:< in ⇒ - rec(tail, if (acc contains out) acc else out :: acc) - case TypeRef(_, _, _ :: tail :: Nil) ⇒ - rec(tail, acc) - case _ ⇒ acc.reverse - } - } - val n = typeOf[Nothing] - if (msg =:= n) List(n) else rec(list, Nil) - } - - /** - * filter from the `required` list of types all which are subtypes of inputs of the ChannelList - */ - 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 (when compiling the macro itself) - 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) - } - - /** - * convert a list of types List(, , ...) into a ChannelList - * ( Channel[, Nothing] :=: Channel[, Nothing] :=: ... :=: TNil ) - */ - final def toChannels(u: Universe)(list: List[u.Type]): u.Type = { - import u._ - def rec(l: List[Type], acc: Type): Type = l match { - case head :: (tail: List[Type]) ⇒ - if (head =:= weakTypeOf[Nothing]) rec(tail, acc) - else - rec(tail, - appliedType(weakTypeOf[:+:[_, _]].typeConstructor, List( - appliedType(weakTypeOf[Tuple2[_, _]].typeConstructor, List( - head, - weakTypeOf[Nothing])), - acc))) - case _ ⇒ acc - } - rec(list.reverse, weakTypeOf[TNil]) - } - -} \ No newline at end of file diff --git a/akka-macros/src/main/scala/akka/channels/package.scala b/akka-macros/src/main/scala/akka/channels/package.scala deleted file mode 100644 index bcd3930d8d..0000000000 --- a/akka-macros/src/main/scala/akka/channels/package.scala +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (C) 2009-2012 Typesafe Inc. - */ - -package akka - -import language.implicitConversions -import akka.actor.ActorRef - -package object channels { - implicit def actorRef2Ops(ref: ActorRef) = new Values.ActorRefOps(ref) -} - -package channels { - import akka.util.Timeout - import akka.pattern.ask - import scala.concurrent.{ ExecutionContext, Future } - import scala.reflect.runtime.{ universe ⇒ ru } - - sealed trait ChannelList - sealed trait TNil extends ChannelList - sealed trait :+:[A <: (_, _), B <: ChannelList] extends ChannelList - - object Values { - class ActorRefOps(val ref: ActorRef) extends AnyVal { - def narrow[C <: ChannelList](implicit timeout: Timeout, ec: ExecutionContext, tt: ru.TypeTag[C]): Future[ChannelRef[C]] = { - import Channels._ - ref ? CheckType(tt) map { - case CheckTypeACK ⇒ new ChannelRef[C](ref) - case CheckTypeNAK(error) ⇒ throw NarrowingException(error) - } - } - } - - class WrappedMessage[T <: ChannelList](val value: Any) extends AnyVal - } -} \ No newline at end of file diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index b5d81b16e3..c3e5c3fc38 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -73,7 +73,7 @@ object AkkaBuild extends Build { generatedPdf in Sphinx <<= generatedPdf in Sphinx in LocalProject(docs.id) map identity ), - aggregate = Seq(actor, testkit, actorTests, dataflow, remote, remoteTests, camel, cluster, slf4j, agent, transactor, mailboxes, zeroMQ, kernel, akkaSbtPlugin, osgi, osgiAries, docs, contrib, samples, macros, macroTests) + aggregate = Seq(actor, testkit, actorTests, dataflow, remote, remoteTests, camel, cluster, slf4j, agent, transactor, mailboxes, zeroMQ, kernel, akkaSbtPlugin, osgi, osgiAries, docs, contrib, samples, channels, channelsTests) ) lazy val actor = Project( @@ -409,19 +409,19 @@ object AkkaBuild extends Build { ) ) configs (MultiJvm) - lazy val macros = Project( - id = "akka-macros", - base = file("akka-macros"), + lazy val channels = Project( + id = "akka-channels", + base = file("akka-channels"), dependencies = Seq(actor), settings = defaultSettings ++ Seq( libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-reflect" % _) ) ) - lazy val macroTests = Project( - id = "akka-macro-tests", - base = file("akka-macro-tests"), - dependencies = Seq(macros, testkit % "compile;test->test"), + lazy val channelsTests = Project( + id = "akka-channels-tests", + base = file("akka-channels-tests"), + dependencies = Seq(channels, testkit % "compile;test->test"), settings = defaultSettings ++ Seq( libraryDependencies <+= (scalaVersion)("org.scala-lang" % "scala-compiler" % _) ) From e55e57060b3f88f7183112d3c9d97b2643942771 Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 23 Jan 2013 15:42:48 +0100 Subject: [PATCH 15/26] finally found out how to splice types - add Helpers.weakTT for producing a WeakTypeTag from a tpe - use that to simplify Ask.scala (the others will follow) - and this way make forwarding actually work --- .../scala/akka/channels/ChannelSpec.scala | 22 ++++- .../main/scala/akka/channels/ChannelRef.scala | 2 +- .../src/main/scala/akka/channels/Ops.scala | 4 +- .../main/scala/akka/channels/macros/Ask.scala | 90 ++++++++++--------- .../scala/akka/channels/macros/Helpers.scala | 14 +++ .../scala/akka/channels/macros/Tell.scala | 15 ++-- 6 files changed, 92 insertions(+), 55 deletions(-) diff --git a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index 783f59fb6e..0bf28903c0 100644 --- a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -82,9 +82,17 @@ object ChannelSpec { // compile test for polymorphic actors class WriteOnly[T1: ru.TypeTag, T2: ru.TypeTag](target: ChannelRef[(T1, T2) :+: TNil]) extends Channels[TNil, (D, D) :+: (T1, T2) :+: TNil] { - channel[D] { (d, snd) ⇒ snd <-!- d } implicit val t = Timeout(1.second) - channel[T1] { (x, snd) ⇒ x -?-> target } + import akka.pattern.ask + + channel[D] { (d, snd) ⇒ snd <-!- d } + + channel[T1] { (x, snd) ⇒ x -?-> target -!-> snd -!-> snd } + } + + // companion to WriteOnly for testing pass-through + class EchoTee(target: ActorRef) extends Channels[TNil, (C, C) :+: TNil] { + channel[C] { (c, snd) ⇒ target ! C1; snd <-!- C1 } } class MissingChannel extends Channels[TNil, (A, A) :+: (B, B) :+: TNil] { @@ -376,6 +384,16 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, lastSender must be(wrap.actorRef) } + "allow wrapping of ChannelsRefs with replies" in { + val probe = TestProbe() + val target = ChannelExt(system).actorOf(new EchoTee(probe.ref)) + val wrap = ChannelExt(system).actorOf(new WriteOnly[C, C](target)) + C -!-> wrap + expectMsg(C1) + expectMsg(C1) + probe.expectMsg(C1) + } + "support typed ask" in { val t = ChannelExt(system).actorOf(new Tester) implicit val timeout = Timeout(1.second) diff --git a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala index 276371ece7..acdce8e059 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala @@ -23,7 +23,7 @@ class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { def <-!-[M](msg: M): Unit = macro macros.Tell.impl[T, M] - def <-?-[M](msg: M): Future[_] = macro macros.Ask.impl[T, M] + def <-?-[M](msg: M): Future[_] = macro macros.Ask.impl[Any, T, M] def narrow[C <: ChannelList]: ChannelRef[C] = macro macros.Narrow.impl[C, T] diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala index 21ad45eec9..6e631cdbf2 100644 --- a/akka-channels/src/main/scala/akka/channels/Ops.scala +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -25,12 +25,12 @@ class ActorRefOps(val ref: ActorRef) extends AnyVal { class FutureOps[T](val future: Future[T]) extends AnyVal { def -!->[C <: ChannelList](channel: ChannelRef[C]): Future[T] = macro macros.Tell.futureImpl[C, T] - def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.futureImpl[C, T] + def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.futureImpl[Any, C, T] } class AnyOps[T](val value: T) extends AnyVal { def -!->[C <: ChannelList](channel: ChannelRef[C]): Unit = macro macros.Tell.opsImpl[C, T] - def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.opsImpl[C, T] + def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.opsImpl[Any, C, T] } class WrappedMessage[T <: ChannelList](val value: Any) extends AnyVal diff --git a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala index 2c31f65ae4..2b0b30b040 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala @@ -9,55 +9,61 @@ import scala.reflect.macros.Context import scala.reflect.api.Universe import akka.actor.ActorRef import akka.dispatch.ExecutionContexts +import scala.reflect.api.{ TypeCreator } object Ask { import Helpers._ - def impl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = ChannelRef[T] - })(msg: c.Expr[M]): c.Expr[Future[_]] = { + def impl[A, T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[T] + })(msg: c.Expr[M]): c.Expr[Future[A]] = { import c.universe._ - askTree(c)(weakTypeOf[M], weakTypeOf[T])(reify(c.prefix.splice.actorRef), msg) - } - def opsImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = AnyOps[M] - })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[_]] = { - import c.universe._ - askTree(c)(weakTypeOf[M], weakTypeOf[T])(reify(channel.splice.actorRef), reify(c.prefix.splice.value)) - } - - def futureImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = FutureOps[M] - })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[_]] = { - import c.universe._ - val tree = askTree(c)(weakTypeOf[M], weakTypeOf[T])(c.Expr(Ident("c$1")), c.Expr(Ident("x$1"))) - reify({ - val c$1 = channel.splice.actorRef - c.prefix.splice.future.flatMap(x$1 ⇒ tree.splice)(ExecutionContexts.sameThreadExecutionContext) - }) - } - - def askTree[M](c: Context with Singleton)(msgT: c.universe.Type, chT: c.universe.Type)(target: c.Expr[ActorRef], msg: c.Expr[M]): c.Expr[Future[_]] = { - import c.universe._ + val chT = weakTypeOf[T] + val msgT = weakTypeOf[M] val out = replyChannels(c.universe)(chT, msgT) - if (out.isEmpty) { - c.error(c.enclosingPosition, s"This ChannelRef does not support messages of type $msgT") - reify(null) - } else { - val timeout = c.inferImplicitValue(typeOf[Timeout]) - if (timeout.isEmpty) - c.error(c.enclosingPosition, s"no implicit akka.util.Timeout found") - val result = appliedType(weakTypeOf[Future[_]].typeConstructor, List(lub(out))) - c.Expr( - TypeApply( - Select( - reify(akka.pattern.ask( - target.splice, msg.splice)( - c.Expr(timeout)(weakTypeTag[Timeout]).splice)).tree, - "asInstanceOf"), - List(TypeTree().setType(result))))(weakTypeTag[Future[_]]) - } + if (out.isEmpty) + abort(c, s"This ChannelRef does not support messages of type $msgT") + + implicit val tA = weakTT[A](c)(c.universe.lub(out)) + reify(askOps[A](c.prefix.splice.actorRef, msg.splice)(imp[Timeout](c).splice)) } + def opsImpl[A, T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = AnyOps[M] + })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[A]] = { + import c.universe._ + + val chT = weakTypeOf[T] + val msgT = weakTypeOf[M] + val out = replyChannels(c.universe)(chT, msgT) + if (out.isEmpty) + abort(c, s"This ChannelRef does not support messages of type $msgT") + + implicit val tA = weakTT[A](c)(c.universe.lub(out)) + reify(askOps[A](channel.splice.actorRef, c.prefix.splice.value)(imp[Timeout](c).splice)) + } + + def futureImpl[A, T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { + type PrefixType = FutureOps[M] + })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[A]] = { + import c.universe._ + + val chT = weakTypeOf[T] + val msgT = weakTypeOf[M] + val reply = replyChannels(c.universe)(chT, msgT) match { + case Nil ⇒ abort(c, s"This ChannelRef does not support messages of type $msgT") + case x :: Nil ⇒ x + case xs ⇒ toChannels(c.universe)(xs) + } + + implicit val tA = weakTT[A](c)(reply) + reify(askFuture[M, A](channel.splice.actorRef, c.prefix.splice.future)(imp[Timeout](c).splice)) + } + + @inline def askOps[T](target: ActorRef, msg: Any)(implicit t: Timeout): Future[T] = akka.pattern.ask(target, msg).asInstanceOf[Future[T]] + + def askFuture[T1, T2](target: ActorRef, future: Future[T1])(implicit t: Timeout): Future[T2] = + future.flatMap(akka.pattern.ask(target, _).asInstanceOf[Future[T2]])(ExecutionContexts.sameThreadExecutionContext) + } \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala index c72bd53392..549640ce32 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -7,6 +7,7 @@ import scala.reflect.runtime.{ universe ⇒ ru } import ru.TypeTag import scala.reflect.macros.Context import scala.reflect.api.Universe +import scala.reflect.api.TypeCreator object Helpers { @@ -19,6 +20,19 @@ object Helpers { def error(c: Context, msg: String) = c.error(c.enclosingPosition, msg) def abort(c: Context, msg: String) = c.abort(c.enclosingPosition, msg) + def imp[T: c.WeakTypeTag](c: Context): c.Expr[T] = { + import c.universe._ + c.Expr[T](TypeApply(Ident("implicitly"), List(TypeTree().setType(weakTypeOf[T])))) + } + + def weakTT[T](c: Context)(tpe: c.universe.Type): c.WeakTypeTag[T] = + c.universe.WeakTypeTag[T](c.mirror, new TypeCreator { + def apply[U <: Universe with Singleton](m: scala.reflect.api.Mirror[U]) = { + val imp = m.universe.mkImporter(c.universe) + imp.importType(tpe) + } + }) + def checkUnique(u: Universe)(channel: u.Type, list: u.Type): Option[String] = { val channels = inputChannels(u)(list) groupBy (_.erasure) val dupes = channels.get(channel.erasure).getOrElse(Nil).filterNot(_ =:= channel) diff --git a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala index ce0d56b134..e01fd80266 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala @@ -44,16 +44,15 @@ object Tell { verify(c)(senderTree, c.universe.weakTypeOf[M], tS, tT) - c.universe.reify( - { - val s$1 = sender.splice - c.prefix.splice.future.andThen { - case Success(s) ⇒ channel.splice.actorRef.tell(s, s$1) - case _ ⇒ - }(ExecutionContexts.sameThreadExecutionContext) - }) + c.universe.reify(pipeTo[M](c.prefix.splice, channel.splice, sender.splice)) } + @inline def pipeTo[M](f: FutureOps[M], c: ChannelRef[_], snd: ActorRef): Future[M] = + f.future.andThen { + case Success(s) ⇒ c.actorRef.tell(s, snd) + case _ ⇒ + }(ExecutionContexts.sameThreadExecutionContext) + def getSenderChannel(c: Context): (c.universe.Type, c.Tree, c.Expr[ActorRef]) = { val replyChannel = c.inferImplicitValue(c.typeOf[ChannelRef[_]]) if (!replyChannel.isEmpty) { From f5934b9ccae4e2c9adc0f44d200fd44282e7f087 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 25 Jan 2013 22:00:11 +0100 Subject: [PATCH 16/26] the great beautification - reify() all trees, no bricolage - use rediscovered c.TypeTag factory to splice in calculated types - give meaningful names to type parameters and humunguosly explode val names --- .../main/scala/akka/channels/Channels.scala | 11 ++- .../main/scala/akka/channels/macros/Ask.scala | 54 ++++++------- .../scala/akka/channels/macros/Channel.scala | 75 +++++++------------ .../akka/channels/macros/CreateChild.scala | 21 +++--- .../scala/akka/channels/macros/Helpers.scala | 8 +- .../scala/akka/channels/macros/Narrow.scala | 10 +-- .../scala/akka/channels/macros/Tell.scala | 40 +++++----- 7 files changed, 99 insertions(+), 120 deletions(-) diff --git a/akka-channels/src/main/scala/akka/channels/Channels.scala b/akka-channels/src/main/scala/akka/channels/Channels.scala index 65c9c9040c..cc1ea5115c 100644 --- a/akka-channels/src/main/scala/akka/channels/Channels.scala +++ b/akka-channels/src/main/scala/akka/channels/Channels.scala @@ -87,16 +87,19 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { * } * }}} */ - def channel[T]: Channels[P, C]#Behaviorist[Nothing, T] = macro macros.Channel.impl[T, C, P] + def channel[T]: (Nothing ⇒ Unit) = macro macros.Channel.impl[ChannelList, ChannelList, T, C, P] - class Behaviorist[-R, Ch](tt: ru.TypeTag[Ch], wrapped: Boolean) { + def behaviorist[R, Ch: ru.TypeTag](wrapped: Boolean): (R ⇒ Unit) = new Behaviorist[R, Ch](wrapped) + private class Behaviorist[-R, Ch: ru.TypeTag](wrapped: Boolean) extends (R ⇒ Unit) { private def ff(recv: R): FF = if (wrapped) F1(recv.asInstanceOf[(WrappedMessage[ChannelList], ChannelRef[ChannelList]) ⇒ Unit]) else F2(recv.asInstanceOf[(Any, ChannelRef[ChannelList]) ⇒ Unit]) - def apply(recv: R): Unit = + def apply(recv: R): Unit = { + val tt = implicitly[ru.TypeTag[Ch]] behavior ++= (for (t ← inputChannels(ru)(tt.tpe)) yield tt.mirror.runtimeClass(t.widen) -> ff(recv)) + } } /* @@ -105,7 +108,7 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { * I’d like to keep this a trait, but traits cannot have constructor * arguments, not even TypeTags. */ - protected var channelListTypeTag: TypeTag[C] = _ + var channelListTypeTag: TypeTag[C] = _ /** * Sort so that subtypes always precede their supertypes, but without diff --git a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala index 2b0b30b040..a6129ffd77 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala @@ -14,51 +14,51 @@ import scala.reflect.api.{ TypeCreator } object Ask { import Helpers._ - def impl[A, T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = ChannelRef[T] - })(msg: c.Expr[M]): c.Expr[Future[A]] = { + def impl[ReturnT, Channel <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[Channel] + })(msg: c.Expr[Msg]): c.Expr[Future[ReturnT]] = { import c.universe._ - val chT = weakTypeOf[T] - val msgT = weakTypeOf[M] - val out = replyChannels(c.universe)(chT, msgT) + val tpeChannel = weakTypeOf[Channel] + val tpeMsg = weakTypeOf[Msg] + val out = replyChannels(c.universe)(tpeChannel, tpeMsg) if (out.isEmpty) - abort(c, s"This ChannelRef does not support messages of type $msgT") + abort(c, s"This ChannelRef does not support messages of type $tpeMsg") - implicit val tA = weakTT[A](c)(c.universe.lub(out)) - reify(askOps[A](c.prefix.splice.actorRef, msg.splice)(imp[Timeout](c).splice)) + implicit val ttReturn = c.TypeTag[ReturnT](c.universe.lub(out)) + reify(askOps[ReturnT](c.prefix.splice.actorRef, msg.splice)(imp[Timeout](c).splice)) } - def opsImpl[A, T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = AnyOps[M] - })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[A]] = { + def opsImpl[ReturnT, Channel <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { + type PrefixType = AnyOps[Msg] + })(channel: c.Expr[ChannelRef[Channel]]): c.Expr[Future[ReturnT]] = { import c.universe._ - val chT = weakTypeOf[T] - val msgT = weakTypeOf[M] - val out = replyChannels(c.universe)(chT, msgT) + val tpeChannel = weakTypeOf[Channel] + val tpeMsg = weakTypeOf[Msg] + val out = replyChannels(c.universe)(tpeChannel, tpeMsg) if (out.isEmpty) - abort(c, s"This ChannelRef does not support messages of type $msgT") + abort(c, s"This ChannelRef does not support messages of type $tpeMsg") - implicit val tA = weakTT[A](c)(c.universe.lub(out)) - reify(askOps[A](channel.splice.actorRef, c.prefix.splice.value)(imp[Timeout](c).splice)) + implicit val ttReturn = c.TypeTag[ReturnT](c.universe.lub(out)) + reify(askOps[ReturnT](channel.splice.actorRef, c.prefix.splice.value)(imp[Timeout](c).splice)) } - def futureImpl[A, T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = FutureOps[M] - })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[A]] = { + def futureImpl[ReturnT, Channel <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { + type PrefixType = FutureOps[Msg] + })(channel: c.Expr[ChannelRef[Channel]]): c.Expr[Future[ReturnT]] = { import c.universe._ - val chT = weakTypeOf[T] - val msgT = weakTypeOf[M] - val reply = replyChannels(c.universe)(chT, msgT) match { - case Nil ⇒ abort(c, s"This ChannelRef does not support messages of type $msgT") + val tpeChannel = weakTypeOf[Channel] + val tpeMsg = weakTypeOf[Msg] + val reply = replyChannels(c.universe)(tpeChannel, tpeMsg) match { + case Nil ⇒ abort(c, s"This ChannelRef does not support messages of type $tpeMsg") case x :: Nil ⇒ x case xs ⇒ toChannels(c.universe)(xs) } - implicit val tA = weakTT[A](c)(reply) - reify(askFuture[M, A](channel.splice.actorRef, c.prefix.splice.future)(imp[Timeout](c).splice)) + implicit val ttReturn = c.TypeTag[ReturnT](reply) + reify(askFuture[Msg, ReturnT](channel.splice.actorRef, c.prefix.splice.future)(imp[Timeout](c).splice)) } @inline def askOps[T](target: ActorRef, msg: Any)(implicit t: Timeout): Future[T] = akka.pattern.ask(target, msg).asInstanceOf[Future[T]] diff --git a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala index f5234778de..68ccec4dfb 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala @@ -1,8 +1,7 @@ package akka.channels.macros import akka.channels._ -import scala.reflect.runtime.{ universe ⇒ ru } -import ru.TypeTag +import scala.reflect.runtime.universe import scala.reflect.macros.Context import scala.reflect.api.Universe @@ -18,59 +17,41 @@ object Channel { * C is the channel list of the enclosing Channels * P is the parent channel list */ - def impl[T: c.WeakTypeTag, C <: ChannelList: c.WeakTypeTag, P <: ChannelList: c.WeakTypeTag]( + def impl[ReplyChannels <: ChannelList, MsgTChan <: ChannelList, MsgT: c.WeakTypeTag, MyChannels <: ChannelList: c.WeakTypeTag, ParentChannels <: ChannelList: c.WeakTypeTag]( c: Context { - type PrefixType = Channels[P, C] - }): c.Expr[Channels[P, C]#Behaviorist[Nothing, T]] = { + type PrefixType = Channels[ParentChannels, MyChannels] + }): c.Expr[(Nothing ⇒ Unit)] = { - val tT = c.weakTypeOf[T] - val tC = c.weakTypeOf[C] + val tpeMsgT = c.weakTypeOf[MsgT] + val tpeMyChannels = c.weakTypeOf[MyChannels] import c.universe._ - val undefined = missingChannels(c.universe)(tC, inputChannels(c.universe)(tT)) + val undefined = missingChannels(c.universe)(tpeMyChannels, inputChannels(c.universe)(tpeMsgT)) if (undefined.nonEmpty) { - c.error(c.enclosingPosition, s"no channel defined for types ${undefined mkString ", "}") - reify(null) + c.abort(c.enclosingPosition, s"no channel defined for types ${undefined mkString ", "}") } else { - checkUnique(c.universe)(tT, tC) foreach (c.error(c.enclosingPosition, _)) - val channels = toChannels(c.universe)(replyChannels(c.universe)(tC, tT)) - val (receive, wrapped) = - if (tT <:< typeOf[ChannelList]) { - appliedType(typeOf[Function2[_, _, _]].typeConstructor, List( - appliedType(typeOf[WrappedMessage[_]].typeConstructor, List(tT)), - appliedType(typeOf[ChannelRef[_]].typeConstructor, List(channels)), - typeOf[Unit])) -> true - } else { - appliedType(typeOf[Function2[_, _, _]].typeConstructor, List( - tT, - appliedType(typeOf[ChannelRef[_]].typeConstructor, List(channels)), - typeOf[Unit])) -> false + checkUnique(c.universe)(tpeMsgT, tpeMyChannels) foreach (c.error(c.enclosingPosition, _)) + val channels = toChannels(c.universe)(replyChannels(c.universe)(tpeMyChannels, tpeMsgT)) + val wrapped = tpeMsgT <:< typeOf[ChannelList] + implicit val ttMyChannels = c.TypeTag[MyChannels](tpeMyChannels) + implicit val ttReplyChannels = c.TypeTag[ReplyChannels](channels) + implicit val ttMsgT = c.TypeTag[MsgT](tpeMsgT) + implicit val ttMsgTChan = c.TypeTag[MsgTChan](tpeMsgT) // this is MsgT reinterpreted as <: ChannelList + val prepTree = reify(if (c.prefix.splice.channelListTypeTag == null) + c.prefix.splice.channelListTypeTag = universe.typeTag[MyChannels]) + if (wrapped) + reify { + prepTree.splice + c.prefix.splice.behaviorist[(WrappedMessage[MsgTChan], ChannelRef[ReplyChannels]) ⇒ Unit, MsgT]( + bool(c, wrapped).splice)(universe.typeTag[MsgT]) + } + else + reify { + prepTree.splice + c.prefix.splice.behaviorist[(MsgT, ChannelRef[ReplyChannels]) ⇒ Unit, MsgT]( + bool(c, wrapped).splice)(universe.typeTag[MsgT]) } - c.Expr( - Block(List( - If( - { - val cltt = c.Expr(Select(c.prefix.tree, "channelListTypeTag")) - reify(cltt.splice == null).tree - }, - Apply( - Select(c.prefix.tree, "channelListTypeTag_$eq"), - List(TypeApply( - Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), - List(TypeTree().setType(c.weakTypeOf[C]))))), - c.literalUnit.tree)), - Apply( - Select( - New(AppliedTypeTree(Select(c.prefix.tree, newTypeName("Behaviorist")), List( - TypeTree().setType(receive), - TypeTree().setType(tT)))), - nme.CONSTRUCTOR), - List( - TypeApply( - Select(Select(Select(Select(Select(Ident("scala"), "reflect"), "runtime"), nme.PACKAGE), "universe"), "typeTag"), - List(TypeTree().setType(tT))), - Literal(Constant(wrapped)))))) } } diff --git a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala index 94b3ba8abc..1d38f4c52c 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala @@ -10,25 +10,26 @@ import akka.actor.Props object CreateChild { import Helpers._ - def impl[C <: ChannelList: c.WeakTypeTag, Pa <: ChannelList: c.WeakTypeTag, Ch <: ChannelList: c.WeakTypeTag]( + def impl[MyChannels <: ChannelList: c.WeakTypeTag, ParentChannels <: ChannelList: c.WeakTypeTag, ChildChannels <: ChannelList: c.WeakTypeTag]( c: Context { - type PrefixType = Channels[_, C] - })(factory: c.Expr[Channels[Pa, Ch]]): c.Expr[ChannelRef[Ch]] = { + type PrefixType = Channels[_, MyChannels] + })(factory: c.Expr[Channels[ParentChannels, ChildChannels]]): c.Expr[ChannelRef[ChildChannels]] = { import c.universe._ - if (weakTypeOf[Pa] =:= weakTypeOf[Nothing]) { + + if (weakTypeOf[ParentChannels] =:= weakTypeOf[Nothing]) { c.abort(c.enclosingPosition, "Parent argument must not be Nothing") } - if (weakTypeOf[Ch] =:= weakTypeOf[Nothing]) { + if (weakTypeOf[ChildChannels] =:= weakTypeOf[Nothing]) { c.abort(c.enclosingPosition, "channel list must not be Nothing") } - val missing = missingChannels(c.universe)(weakTypeOf[C], inputChannels(c.universe)(weakTypeOf[Pa])) + + val missing = missingChannels(c.universe)(weakTypeOf[MyChannels], inputChannels(c.universe)(weakTypeOf[ParentChannels])) if (missing.isEmpty) { - implicit val t = c.TypeTag[Ch](c.weakTypeOf[Ch]) - reify(new ChannelRef[Ch](c.prefix.splice.context.actorOf(Props(factory.splice)))) + implicit val t = c.TypeTag[ChildChannels](c.weakTypeOf[ChildChannels]) + reify(new ChannelRef[ChildChannels](c.prefix.splice.context.actorOf(Props(factory.splice)))) } else { - c.error(c.enclosingPosition, s"This actor cannot support a child requiring channels ${missing mkString ", "}") - reify(???) + c.abort(c.enclosingPosition, s"This actor cannot support a child requiring channels ${missing mkString ", "}") } } diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala index 549640ce32..94b92040e6 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -25,13 +25,7 @@ object Helpers { c.Expr[T](TypeApply(Ident("implicitly"), List(TypeTree().setType(weakTypeOf[T])))) } - def weakTT[T](c: Context)(tpe: c.universe.Type): c.WeakTypeTag[T] = - c.universe.WeakTypeTag[T](c.mirror, new TypeCreator { - def apply[U <: Universe with Singleton](m: scala.reflect.api.Mirror[U]) = { - val imp = m.universe.mkImporter(c.universe) - imp.importType(tpe) - } - }) + def bool(c: Context, b: Boolean): c.Expr[Boolean] = c.Expr[Boolean](c.universe.Literal(c.universe.Constant(b))) def checkUnique(u: Universe)(channel: u.Type, list: u.Type): Option[String] = { val channels = inputChannels(u)(list) groupBy (_.erasure) diff --git a/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala b/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala index 2f9f6d3db5..f4b74b49ad 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala @@ -9,16 +9,16 @@ import scala.reflect.api.Universe object Narrow { import Helpers._ - def impl[C <: ChannelList: c.WeakTypeTag, T <: ChannelList: c.WeakTypeTag]( + def impl[Desired <: ChannelList: c.WeakTypeTag, MyChannels <: ChannelList: c.WeakTypeTag]( c: Context { - type PrefixType = ChannelRef[T] - }): c.Expr[ChannelRef[C]] = { + type PrefixType = ChannelRef[MyChannels] + }): c.Expr[ChannelRef[Desired]] = { import c.{ universe ⇒ u } - narrowCheck(u)(u.weakTypeOf[T], u.weakTypeOf[C]) match { + narrowCheck(u)(u.weakTypeOf[MyChannels], u.weakTypeOf[Desired]) match { case Nil ⇒ // okay case err :: Nil ⇒ c.error(c.enclosingPosition, err) case list ⇒ c.error(c.enclosingPosition, list mkString ("multiple errors:\n - ", "\n - ", "")) } - u.reify(c.prefix.splice.asInstanceOf[ChannelRef[C]]) + u.reify(c.prefix.splice.asInstanceOf[ChannelRef[Desired]]) } } \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala index e01fd80266..4395cb77ef 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala @@ -14,40 +14,40 @@ import akka.dispatch.ExecutionContexts object Tell { import Helpers._ - def impl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = ChannelRef[T] - })(msg: c.Expr[M]): c.Expr[Unit] = { - val tT = c.universe.weakTypeOf[T] - val (tS, senderTree, sender) = getSenderChannel(c) + def impl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { + type PrefixType = ChannelRef[MyChannels] + })(msg: c.Expr[Msg]): c.Expr[Unit] = { + val tpeMyChannels = c.universe.weakTypeOf[MyChannels] + val (tpeSender, senderTree, sender) = getSenderChannel(c) - verify(c)(senderTree, c.universe.weakTypeOf[M], tS, tT) + verify(c)(senderTree, c.universe.weakTypeOf[Msg], tpeSender, tpeMyChannels) c.universe.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice)) } - def opsImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = AnyOps[M] - })(channel: c.Expr[ChannelRef[T]]): c.Expr[Unit] = { - val tT = c.universe.weakTypeOf[T] - val (tS, senderTree, sender) = getSenderChannel(c) + def opsImpl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { + type PrefixType = AnyOps[Msg] + })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Unit] = { + val tpeMyChannels = c.universe.weakTypeOf[MyChannels] + val (tpeSender, senderTree, sender) = getSenderChannel(c) - verify(c)(senderTree, c.universe.weakTypeOf[M], tS, tT) + verify(c)(senderTree, c.universe.weakTypeOf[Msg], tpeSender, tpeMyChannels) c.universe.reify(channel.splice.actorRef.tell(c.prefix.splice.value, sender.splice)) } - def futureImpl[T <: ChannelList: c.WeakTypeTag, M: c.WeakTypeTag](c: Context { - type PrefixType = FutureOps[M] - })(channel: c.Expr[ChannelRef[T]]): c.Expr[Future[M]] = { - val tT = c.universe.weakTypeOf[T] - val (tS, senderTree, sender) = getSenderChannel(c) + def futureImpl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { + type PrefixType = FutureOps[Msg] + })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Future[Msg]] = { + val tpeMyChannels = c.universe.weakTypeOf[MyChannels] + val (tpeSender, senderTree, sender) = getSenderChannel(c) - verify(c)(senderTree, c.universe.weakTypeOf[M], tS, tT) + verify(c)(senderTree, c.universe.weakTypeOf[Msg], tpeSender, tpeMyChannels) - c.universe.reify(pipeTo[M](c.prefix.splice, channel.splice, sender.splice)) + c.universe.reify(pipeTo[Msg](c.prefix.splice, channel.splice, sender.splice)) } - @inline def pipeTo[M](f: FutureOps[M], c: ChannelRef[_], snd: ActorRef): Future[M] = + @inline def pipeTo[Msg](f: FutureOps[Msg], c: ChannelRef[_], snd: ActorRef): Future[Msg] = f.future.andThen { case Success(s) ⇒ c.actorRef.tell(s, snd) case _ ⇒ From 6c1edc1f672e1232945d22bb0c94931f3ebedbe2 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 26 Jan 2013 21:12:51 +0100 Subject: [PATCH 17/26] the great wrapping - you can tell/ask WrappedMessage and it will be checked precisely and sent naked - ask() always returns Future[WrappedMessage[_ <: ChannelList, LUB]] - FutureOps.lub will collapse such a Future into Future[LUB] - sending a Future[WrappedMessage[_, _]] to a ChannelRef will check it precisely and send naked --- .../scala/akka/channels/ChannelSpec.scala | 152 +++++++++++++++++- .../main/scala/akka/channels/ChannelRef.scala | 2 +- .../main/scala/akka/channels/Channels.scala | 8 +- .../src/main/scala/akka/channels/Ops.scala | 11 +- .../main/scala/akka/channels/macros/Ask.scala | 97 +++++++---- .../scala/akka/channels/macros/Channel.scala | 16 +- .../scala/akka/channels/macros/Helpers.scala | 33 +++- .../scala/akka/channels/macros/Tell.scala | 21 ++- 8 files changed, 280 insertions(+), 60 deletions(-) diff --git a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index 0bf28903c0..b24c0ece3d 100644 --- a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -90,6 +90,17 @@ object ChannelSpec { channel[T1] { (x, snd) ⇒ x -?-> target -!-> snd -!-> snd } } + // compile test for whole-channel polymorphism + class Poly[T <: ChannelList: ru.TypeTag](target: ChannelRef[T]) extends Channels[TNil, (A, A) :+: (B, B) :+: T] { + channel[A] { (a, snd) ⇒ val x: A = a } + channel[T] { (x, snd) ⇒ + val t: T = x.value + // val f = target <-?- x + } + import language.existentials + channel[(A, _) :+: (B, _) :+: TNil] { (x, snd) ⇒ val m: Msg = x.value } + } + // companion to WriteOnly for testing pass-through class EchoTee(target: ActorRef) extends Channels[TNil, (C, C) :+: TNil] { channel[C] { (c, snd) ⇒ target ! C1; snd <-!- C1 } @@ -397,14 +408,14 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "support typed ask" in { val t = ChannelExt(system).actorOf(new Tester) implicit val timeout = Timeout(1.second) - val r: Future[C] = t <-?- A + val r: Future[C] = (t <-?- A).lub Await.result(r, 1.second) must be(C) } "support typed ask with multiple reply channels" in { val t = ChannelExt(system).actorOf(new SubChannels) implicit val timeout = Timeout(1.second) - val r: Future[Msg] = t <-?- A1 + val r: Future[Msg] = (t <-?- A1).lub Await.result(r, 1.second) must be(B) } @@ -443,4 +454,141 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } + "A WrappedMessage" must { + + "be sendable to a ChannelRef" in { + implicit val selfChannel = ChannelExt(system).actorOf(new Channels[TNil, (C, Nothing) :+:(D, Nothing) :+: TNil] { + channel[C] { (c, snd) ⇒ testActor ! c } + channel[D] { (d, snd) ⇒ testActor ! d } + }) + val t = ChannelExt(system).actorOf(new Tester) + val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) + val b = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](B) + t <-!- a + expectMsg(C) + a -!-> t + expectMsg(C) + t <-!- b + expectMsg(D) + b -!-> t + expectMsg(D) + } + + "not be sendable with wrong channels" when { + "sending wrong first directly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[(Any, Nothing) :+: TNil](null) + |new ChannelRef[(A, Nothing) :+: TNil](null) <-!- new WrappedMessage[(B, Nothing) :+: (A, Nothing) :+: TNil, Msg](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + "sending wrong first indirectly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[(Any, Nothing) :+: TNil](null) + |new WrappedMessage[(B, Nothing) :+: (A, Nothing) :+: TNil, Msg](null) -!-> new ChannelRef[(A, Nothing) :+: TNil](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + "sending wrong second directly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[(Any, Nothing) :+: TNil](null) + |new ChannelRef[(A, Nothing) :+: TNil](null) <-!- new WrappedMessage[(A, Nothing) :+: (B, Nothing) :+: TNil, Msg](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + "sending wrong second indirectly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val s = new ChannelRef[(Any, Nothing) :+: TNil](null) + |new WrappedMessage[(A, Nothing) :+: (B, Nothing) :+: TNil, Msg](null) -!-> new ChannelRef[(A, Nothing) :+: TNil](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + } + + "be askable to a ChannelRef" in { + implicit val timeout = Timeout(1.second) + val t = ChannelExt(system).actorOf(new Tester) + val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) + val b = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](B) + (Await.result(t <-?- a, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(C) + (Await.result(a -?-> t, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(C) + (Await.result(t <-?- b, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(D) + (Await.result(b -?-> t, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(D) + } + + "not be askable with wrong channels" when { + "sending wrong first directly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val t = akka.util.Timeout(null) + |new ChannelRef[(A, Nothing) :+: TNil](null) <-?- new WrappedMessage[(B, Nothing) :+: (A, Nothing) :+: TNil, Msg](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + "sending wrong first indirectly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val t = akka.util.Timeout(null) + |new WrappedMessage[(B, Nothing) :+: (A, Nothing) :+: TNil, Msg](null) -?-> new ChannelRef[(A, Nothing) :+: TNil](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + "sending wrong second directly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val t = akka.util.Timeout(null) + |new ChannelRef[(A, Nothing) :+: TNil](null) <-?- new WrappedMessage[(A, Nothing) :+: (B, Nothing) :+: TNil, Msg](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + "sending wrong second indirectly" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |implicit val t = akka.util.Timeout(null) + |new WrappedMessage[(A, Nothing) :+: (B, Nothing) :+: TNil, Msg](null) -?-> new ChannelRef[(A, Nothing) :+: TNil](null) + """.stripMargin) + }.message must include("target ChannelRef does not support messages of types akka.channels.ChannelSpec.B (at depth 1)") + } + } + + "be LUBbable within a Future" in { + implicit val timeout = Timeout(1.second) + val t = ChannelExt(system).actorOf(new Tester) + val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) + (Await.result((a -?-> t).lub, timeout.duration): Msg) must be(C) + } + + "not be LUBbable if not wrapped" in { + intercept[ToolBoxError] { + eval(""" + |import akka.channels._ + |import ChannelSpec._ + |import scala.concurrent.Future + |null.asInstanceOf[Future[Int]].lub + """.stripMargin) + }.message must include("Cannot prove that Int <:< akka.channels.WrappedMessage") + } + + } + } \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala index acdce8e059..34541a7b8e 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala @@ -23,7 +23,7 @@ class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { def <-!-[M](msg: M): Unit = macro macros.Tell.impl[T, M] - def <-?-[M](msg: M): Future[_] = macro macros.Ask.impl[Any, T, M] + def <-?-[M](msg: M): Future[_] = macro macros.Ask.impl[ChannelList, Any, T, M] def narrow[C <: ChannelList]: ChannelRef[C] = macro macros.Narrow.impl[C, T] diff --git a/akka-channels/src/main/scala/akka/channels/Channels.scala b/akka-channels/src/main/scala/akka/channels/Channels.scala index cc1ea5115c..e3bc14ccbd 100644 --- a/akka-channels/src/main/scala/akka/channels/Channels.scala +++ b/akka-channels/src/main/scala/akka/channels/Channels.scala @@ -73,7 +73,7 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { * Functions for storage in the behavior, to get around erasure */ private trait FF - private case class F1(f: (WrappedMessage[ChannelList], ChannelRef[ChannelList]) ⇒ Unit) extends FF + private case class F1(f: (WrappedMessage[ChannelList, Any], ChannelRef[ChannelList]) ⇒ Unit) extends FF private case class F2(f: (Any, ChannelRef[ChannelList]) ⇒ Unit) extends FF /** @@ -87,13 +87,13 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { * } * }}} */ - def channel[T]: (Nothing ⇒ Unit) = macro macros.Channel.impl[ChannelList, ChannelList, T, C, P] + 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) private class Behaviorist[-R, Ch: ru.TypeTag](wrapped: Boolean) extends (R ⇒ Unit) { private def ff(recv: R): FF = if (wrapped) - F1(recv.asInstanceOf[(WrappedMessage[ChannelList], ChannelRef[ChannelList]) ⇒ Unit]) + F1(recv.asInstanceOf[(WrappedMessage[ChannelList, Any], ChannelRef[ChannelList]) ⇒ Unit]) else F2(recv.asInstanceOf[(Any, ChannelRef[ChannelList]) ⇒ Unit]) def apply(recv: R): Unit = { @@ -152,7 +152,7 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { case None ⇒ default(x) case Some(cls) ⇒ behavior(cls) match { - case F1(f) ⇒ f(new WrappedMessage[ChannelList](x), new ChannelRef(sender)) + case F1(f) ⇒ f(new WrappedMessage[ChannelList, Any](x), new ChannelRef(sender)) case F2(f) ⇒ f(x, new ChannelRef(sender)) } } diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala index 6e631cdbf2..2fea41c5e3 100644 --- a/akka-channels/src/main/scala/akka/channels/Ops.scala +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -7,6 +7,7 @@ import akka.pattern.ask import scala.concurrent.{ ExecutionContext, Future } import scala.reflect.runtime.{ universe ⇒ ru } import scala.util.Success +import akka.dispatch.ExecutionContexts sealed trait ChannelList sealed trait TNil extends ChannelList @@ -25,12 +26,16 @@ class ActorRefOps(val ref: ActorRef) extends AnyVal { class FutureOps[T](val future: Future[T]) extends AnyVal { def -!->[C <: ChannelList](channel: ChannelRef[C]): Future[T] = macro macros.Tell.futureImpl[C, T] - def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.futureImpl[Any, C, T] + def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.futureImpl[ChannelList, Any, C, T] + def lub[LUB](implicit ev: T <:< WrappedMessage[_, LUB]): Future[LUB] = { + implicit val ec = ExecutionContexts.sameThreadExecutionContext + future map (ev(_).value) + } } class AnyOps[T](val value: T) extends AnyVal { def -!->[C <: ChannelList](channel: ChannelRef[C]): Unit = macro macros.Tell.opsImpl[C, T] - def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.opsImpl[Any, C, T] + def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.opsImpl[ChannelList, Any, C, T] } -class WrappedMessage[T <: ChannelList](val value: Any) extends AnyVal +class WrappedMessage[T <: ChannelList, LUB](val value: LUB) extends AnyVal diff --git a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala index a6129ffd77..e33127b78e 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala @@ -14,56 +14,95 @@ import scala.reflect.api.{ TypeCreator } object Ask { import Helpers._ - def impl[ReturnT, Channel <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { - type PrefixType = ChannelRef[Channel] - })(msg: c.Expr[Msg]): c.Expr[Future[ReturnT]] = { + def impl[ // + ReturnChannels <: ChannelList, // the precise type union describing the reply + ReturnLUB, // the least-upper bound for the reply types + Channel <: ChannelList: c.WeakTypeTag, // the channel being asked + Msg: c.WeakTypeTag // the message being sent down the channel + ](c: Context { + type PrefixType = ChannelRef[Channel] + })(msg: c.Expr[Msg]): c.Expr[Future[WrappedMessage[ReturnChannels, ReturnLUB]]] = { import c.universe._ val tpeChannel = weakTypeOf[Channel] val tpeMsg = weakTypeOf[Msg] - val out = replyChannels(c.universe)(tpeChannel, tpeMsg) - if (out.isEmpty) - abort(c, s"This ChannelRef does not support messages of type $tpeMsg") + val unwrapped = unwrapMsgType(c.universe)(tpeMsg) + val out = replyChannels(c.universe)(tpeChannel, unwrapped) - implicit val ttReturn = c.TypeTag[ReturnT](c.universe.lub(out)) - reify(askOps[ReturnT](c.prefix.splice.actorRef, msg.splice)(imp[Timeout](c).splice)) + Tell.verify(c)(null, unwrapped, typeOf[(Any, Nothing) :+: TNil], tpeChannel) + + implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out)) + implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) + reify(askOps[WrappedMessage[ReturnChannels, ReturnLUB]]( + c.prefix.splice.actorRef, toMsg(c)(msg, tpeMsg).splice)(imp[Timeout](c).splice)) } - def opsImpl[ReturnT, Channel <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { - type PrefixType = AnyOps[Msg] - })(channel: c.Expr[ChannelRef[Channel]]): c.Expr[Future[ReturnT]] = { + def opsImpl[ // + ReturnChannels <: ChannelList, // the precise type union describing the reply + ReturnLUB, // the least-upper bound for the reply types + Channel <: ChannelList: c.WeakTypeTag, // the channel being asked + Msg: c.WeakTypeTag // the message being sent down the channel + ](c: Context { + type PrefixType = AnyOps[Msg] + })(channel: c.Expr[ChannelRef[Channel]]): c.Expr[Future[WrappedMessage[ReturnChannels, ReturnLUB]]] = { import c.universe._ val tpeChannel = weakTypeOf[Channel] val tpeMsg = weakTypeOf[Msg] - val out = replyChannels(c.universe)(tpeChannel, tpeMsg) - if (out.isEmpty) - abort(c, s"This ChannelRef does not support messages of type $tpeMsg") + val unwrapped = unwrapMsgType(c.universe)(tpeMsg) + val out = replyChannels(c.universe)(tpeChannel, unwrapped) - implicit val ttReturn = c.TypeTag[ReturnT](c.universe.lub(out)) - reify(askOps[ReturnT](channel.splice.actorRef, c.prefix.splice.value)(imp[Timeout](c).splice)) + Tell.verify(c)(null, unwrapped, typeOf[(Any, Nothing) :+: TNil], tpeChannel) + + implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out)) + implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) + val msg = reify(c.prefix.splice.value) + reify(askOps[WrappedMessage[ReturnChannels, ReturnLUB]]( + channel.splice.actorRef, toMsg(c)(msg, tpeMsg).splice)(imp[Timeout](c).splice)) } - def futureImpl[ReturnT, Channel <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { - type PrefixType = FutureOps[Msg] - })(channel: c.Expr[ChannelRef[Channel]]): c.Expr[Future[ReturnT]] = { + def futureImpl[ // + ReturnChannels <: ChannelList, // the precise type union describing the reply + ReturnLUB, // the least-upper bound for the reply types + Channel <: ChannelList: c.WeakTypeTag, // the channel being asked + Msg: c.WeakTypeTag // the message being sent down the channel + ](c: Context { + type PrefixType = FutureOps[Msg] + })(channel: c.Expr[ChannelRef[Channel]]): c.Expr[Future[WrappedMessage[ReturnChannels, ReturnLUB]]] = { import c.universe._ val tpeChannel = weakTypeOf[Channel] val tpeMsg = weakTypeOf[Msg] - val reply = replyChannels(c.universe)(tpeChannel, tpeMsg) match { - case Nil ⇒ abort(c, s"This ChannelRef does not support messages of type $tpeMsg") - case x :: Nil ⇒ x - case xs ⇒ toChannels(c.universe)(xs) - } + val unwrapped = unwrapMsgType(c.universe)(tpeMsg) + val out = replyChannels(c.universe)(tpeChannel, unwrapped) - implicit val ttReturn = c.TypeTag[ReturnT](reply) - reify(askFuture[Msg, ReturnT](channel.splice.actorRef, c.prefix.splice.future)(imp[Timeout](c).splice)) + Tell.verify(c)(null, unwrapped, typeOf[(Any, Nothing) :+: TNil], tpeChannel) + + implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out)) + implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) + if (tpeMsg <:< typeOf[ChannelList]) + reify(askFutureWrapped[WrappedMessage[ReturnChannels, ReturnLUB]]( + channel.splice.actorRef, c.prefix.splice.future.asInstanceOf[Future[WrappedMessage[TNil, Any]]])(imp[Timeout](c).splice)) + else + reify(askFuture[WrappedMessage[ReturnChannels, ReturnLUB]]( + channel.splice.actorRef, c.prefix.splice.future)(imp[Timeout](c).splice)) } - @inline def askOps[T](target: ActorRef, msg: Any)(implicit t: Timeout): Future[T] = akka.pattern.ask(target, msg).asInstanceOf[Future[T]] + val wrapMessage = (m: Any) ⇒ (new WrappedMessage[TNil, Any](m): Any) - def askFuture[T1, T2](target: ActorRef, future: Future[T1])(implicit t: Timeout): Future[T2] = - future.flatMap(akka.pattern.ask(target, _).asInstanceOf[Future[T2]])(ExecutionContexts.sameThreadExecutionContext) + @inline def askOps[T <: WrappedMessage[_, _]](target: ActorRef, msg: Any)(implicit t: Timeout): Future[T] = { + implicit val ec = ExecutionContexts.sameThreadExecutionContext + akka.pattern.ask(target, msg).map(wrapMessage).asInstanceOf[Future[T]] + } + + def askFuture[T <: WrappedMessage[_, _]](target: ActorRef, future: Future[_])(implicit t: Timeout): Future[T] = { + implicit val ec = ExecutionContexts.sameThreadExecutionContext + future flatMap (m ⇒ akka.pattern.ask(target, m).map(wrapMessage).asInstanceOf[Future[T]]) + } + + def askFutureWrapped[T <: WrappedMessage[_, _]](target: ActorRef, future: Future[WrappedMessage[_, _]])(implicit t: Timeout): Future[T] = { + implicit val ec = ExecutionContexts.sameThreadExecutionContext + future flatMap (w ⇒ akka.pattern.ask(target, w.value).map(wrapMessage).asInstanceOf[Future[T]]) + } } \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala index 68ccec4dfb..2f4a0e2511 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala @@ -17,7 +17,7 @@ object Channel { * C is the channel list of the enclosing Channels * P is the parent channel list */ - def impl[ReplyChannels <: ChannelList, MsgTChan <: ChannelList, MsgT: c.WeakTypeTag, MyChannels <: ChannelList: c.WeakTypeTag, ParentChannels <: ChannelList: c.WeakTypeTag]( + def impl[LUB, ReplyChannels <: ChannelList, MsgTChan <: ChannelList, MsgT: c.WeakTypeTag, MyChannels <: ChannelList: c.WeakTypeTag, ParentChannels <: ChannelList: c.WeakTypeTag]( c: Context { type PrefixType = Channels[ParentChannels, MyChannels] }): c.Expr[(Nothing ⇒ Unit)] = { @@ -33,24 +33,24 @@ object Channel { } else { checkUnique(c.universe)(tpeMsgT, tpeMyChannels) foreach (c.error(c.enclosingPosition, _)) val channels = toChannels(c.universe)(replyChannels(c.universe)(tpeMyChannels, tpeMsgT)) - val wrapped = tpeMsgT <:< typeOf[ChannelList] implicit val ttMyChannels = c.TypeTag[MyChannels](tpeMyChannels) implicit val ttReplyChannels = c.TypeTag[ReplyChannels](channels) implicit val ttMsgT = c.TypeTag[MsgT](tpeMsgT) - implicit val ttMsgTChan = c.TypeTag[MsgTChan](tpeMsgT) // this is MsgT reinterpreted as <: ChannelList val prepTree = reify(if (c.prefix.splice.channelListTypeTag == null) c.prefix.splice.channelListTypeTag = universe.typeTag[MyChannels]) - if (wrapped) + if (tpeMsgT <:< typeOf[ChannelList]) { + implicit val ttMsgTChan = c.TypeTag[MsgTChan](tpeMsgT) // this is MsgT reinterpreted as <: ChannelList + implicit val ttLUB = c.TypeTag[LUB](c.universe.lub(inputChannels(c.universe)(tpeMsgT))) reify { prepTree.splice - c.prefix.splice.behaviorist[(WrappedMessage[MsgTChan], ChannelRef[ReplyChannels]) ⇒ Unit, MsgT]( - bool(c, wrapped).splice)(universe.typeTag[MsgT]) + c.prefix.splice.behaviorist[(WrappedMessage[MsgTChan, LUB], ChannelRef[ReplyChannels]) ⇒ Unit, MsgT]( + bool(c, true).splice)(universe.typeTag[MsgT]) } - else + } else reify { prepTree.splice c.prefix.splice.behaviorist[(MsgT, ChannelRef[ReplyChannels]) ⇒ Unit, MsgT]( - bool(c, wrapped).splice)(universe.typeTag[MsgT]) + bool(c, false).splice)(universe.typeTag[MsgT]) } } } diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala index 94b92040e6..d2c79b381e 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -59,13 +59,15 @@ object Helpers { final def inputChannels(u: Universe)(list: u.Type): List[u.Type] = { import u._ val imp = u.mkImporter(ru) - val cl = imp.importType(ru.typeOf[ChannelList]) - val tnil = imp.importType(ru.typeOf[TNil]) + val tpeChannelList = imp.importType(ru.typeOf[ChannelList]) + val tpeTNil = imp.importType(ru.typeOf[TNil]) def rec(l: u.Type, acc: List[u.Type]): List[u.Type] = l match { case TypeRef(_, _, TypeRef(_, _, in :: _) :: tail :: Nil) ⇒ rec(tail, if (acc contains in) acc else in :: acc) - case last ⇒ if (last =:= tnil) acc.reverse else (last :: acc).reverse + case TypeRef(_, _, ExistentialType(_, TypeRef(_, _, in :: _)) :: tail :: Nil) ⇒ rec(tail, if (acc contains in) acc else in :: acc) + case ExistentialType(_, x) ⇒ rec(x, acc) + case last ⇒ if (last =:= tpeTNil) acc.reverse else (last :: acc).reverse } - if (list <:< cl) rec(list, Nil) + if (list <:< tpeChannelList) rec(list, Nil) else List(list) } @@ -75,9 +77,10 @@ object Helpers { */ final def replyChannels(u: Universe)(list: u.Type, msg: u.Type): List[u.Type] = { import u._ + val msgTypes = inputChannels(u)(msg) def rec(l: Type, acc: List[Type]): List[Type] = { l match { - case TypeRef(_, _, TypeRef(_, _, in :: out :: Nil) :: tail :: Nil) if msg <:< in ⇒ + case TypeRef(_, _, TypeRef(_, _, in :: out :: Nil) :: tail :: Nil) if msgTypes exists (_ <:< in) ⇒ rec(tail, if (acc contains out) acc else out :: acc) case TypeRef(_, _, _ :: tail :: Nil) ⇒ rec(tail, acc) @@ -124,4 +127,24 @@ object Helpers { rec(list.reverse, weakTypeOf[TNil]) } + /** + * takes a message tpe and tree and returns an expression which yields the + * underlying message (i.e. unwraps WrappedMessage if necessary) + */ + final def toMsg(c: Context)(tree: c.Expr[Any], tpe: c.Type): c.Expr[Any] = { + import c.universe._ + if (tpe <:< c.typeOf[WrappedMessage[_, _]]) + c.universe.reify(tree.splice.asInstanceOf[WrappedMessage[TNil, Any]].value) + else tree + } + + final def unwrapMsgType(u: Universe)(msg: u.Type): u.Type = { + import u._ + if (msg <:< typeOf[WrappedMessage[_, _]]) + msg match { + case TypeRef(_, _, x :: _) ⇒ x + } + else msg + } + } \ No newline at end of file diff --git a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala index 4395cb77ef..1686a9b782 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala @@ -18,39 +18,44 @@ object Tell { type PrefixType = ChannelRef[MyChannels] })(msg: c.Expr[Msg]): c.Expr[Unit] = { val tpeMyChannels = c.universe.weakTypeOf[MyChannels] + val tpeMsg = c.universe.weakTypeOf[Msg] val (tpeSender, senderTree, sender) = getSenderChannel(c) - verify(c)(senderTree, c.universe.weakTypeOf[Msg], tpeSender, tpeMyChannels) + verify(c)(senderTree, unwrapMsgType(c.universe)(tpeMsg), tpeSender, tpeMyChannels) - c.universe.reify(c.prefix.splice.actorRef.tell(msg.splice, sender.splice)) + c.universe.reify(c.prefix.splice.actorRef.tell(toMsg(c)(msg, tpeMsg).splice, sender.splice)) } def opsImpl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { type PrefixType = AnyOps[Msg] })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Unit] = { val tpeMyChannels = c.universe.weakTypeOf[MyChannels] + val tpeMsg = c.universe.weakTypeOf[Msg] val (tpeSender, senderTree, sender) = getSenderChannel(c) - verify(c)(senderTree, c.universe.weakTypeOf[Msg], tpeSender, tpeMyChannels) + verify(c)(senderTree, unwrapMsgType(c.universe)(tpeMsg), tpeSender, tpeMyChannels) - c.universe.reify(channel.splice.actorRef.tell(c.prefix.splice.value, sender.splice)) + val msg = c.universe.reify(c.prefix.splice.value) + c.universe.reify(channel.splice.actorRef.tell(toMsg(c)(msg, tpeMsg).splice, sender.splice)) } def futureImpl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { type PrefixType = FutureOps[Msg] })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Future[Msg]] = { val tpeMyChannels = c.universe.weakTypeOf[MyChannels] + val tpeMsg = c.universe.weakTypeOf[Msg] val (tpeSender, senderTree, sender) = getSenderChannel(c) - verify(c)(senderTree, c.universe.weakTypeOf[Msg], tpeSender, tpeMyChannels) + verify(c)(senderTree, unwrapMsgType(c.universe)(tpeMsg), tpeSender, tpeMyChannels) c.universe.reify(pipeTo[Msg](c.prefix.splice, channel.splice, sender.splice)) } @inline def pipeTo[Msg](f: FutureOps[Msg], c: ChannelRef[_], snd: ActorRef): Future[Msg] = f.future.andThen { - case Success(s) ⇒ c.actorRef.tell(s, snd) - case _ ⇒ + case Success(s: WrappedMessage[_, _]) ⇒ c.actorRef.tell(s.value, snd) + case Success(s) ⇒ c.actorRef.tell(s, snd) + case _ ⇒ }(ExecutionContexts.sameThreadExecutionContext) def getSenderChannel(c: Context): (c.universe.Type, c.Tree, c.Expr[ActorRef]) = { @@ -84,6 +89,6 @@ object Tell { } } } - rec(Set(msgT), Set(c.universe.typeOf[Nothing]), 1) + rec(inputChannels(c.universe)(msgT).toSet, Set(c.universe.typeOf[Nothing]), 1) } } \ No newline at end of file From 172a579b3e0a054aa2ed720fffae7bad3b9cab12 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 26 Jan 2013 22:43:36 +0100 Subject: [PATCH 18/26] add ReplyChannels[T] to model fully polymorphic channel forwarding this enables class F[T <: ChannelList : TypeTag](t: ChannelRef[T]) extends Channels[T] { channel[T] { (x, snd) => x -?-> t -!-> snd } } which can then be extended to bunching, filtering, transforming of message streams. --- .../scala/akka/channels/ChannelSpec.scala | 25 ++++++++++++++++--- .../src/main/scala/akka/channels/Ops.scala | 1 + .../scala/akka/channels/macros/Channel.scala | 9 +++++-- .../scala/akka/channels/macros/Helpers.scala | 9 ++++++- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index b24c0ece3d..fbc7df33bf 100644 --- a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -92,13 +92,18 @@ object ChannelSpec { // compile test for whole-channel polymorphism class Poly[T <: ChannelList: ru.TypeTag](target: ChannelRef[T]) extends Channels[TNil, (A, A) :+: (B, B) :+: T] { - channel[A] { (a, snd) ⇒ val x: A = a } + implicit val timeout = Timeout(1.second) channel[T] { (x, snd) ⇒ - val t: T = x.value - // val f = target <-?- x + val xx: WrappedMessage[T, Any] = x + val f: Future[WrappedMessage[(ReplyChannels[T], Nothing) :+: TNil, ReplyChannels[T]]] = target <-?- x + f -!-> snd } import language.existentials - channel[(A, _) :+: (B, _) :+: TNil] { (x, snd) ⇒ val m: Msg = x.value } + channel[(A, _) :+: (B, _) :+: TNil] { (x, snd) ⇒ + val m: Msg = x.value + val c: ChannelRef[TNil] = snd + } + channel[A] { (x, snd) ⇒ x -!-> snd } } // companion to WriteOnly for testing pass-through @@ -452,6 +457,18 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, m must include("akka.channels.ChannelSpec.B") } + "be able to forward fully generic channels" in { + val cd = ChannelExt(system).actorOf(new Channels[TNil, (C, D) :+: TNil] { + channel[C] { (x, snd) ⇒ snd <-!- D } + }) + val t = ChannelExt(system).actorOf(new Poly(cd)) + t <-!- A + expectMsg(A) + t <-!- C + expectMsg(D) + lastSender must be === t.actorRef + } + } "A WrappedMessage" must { diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala index 2fea41c5e3..feb25c8daf 100644 --- a/akka-channels/src/main/scala/akka/channels/Ops.scala +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -12,6 +12,7 @@ import akka.dispatch.ExecutionContexts sealed trait ChannelList sealed trait TNil extends ChannelList sealed trait :+:[A <: (_, _), B <: ChannelList] extends ChannelList +sealed trait ReplyChannels[T <: ChannelList] extends ChannelList class ActorRefOps(val ref: ActorRef) extends AnyVal { import macros.Helpers._ diff --git a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala index 2f4a0e2511..8c93577c74 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala @@ -32,7 +32,9 @@ object Channel { c.abort(c.enclosingPosition, s"no channel defined for types ${undefined mkString ", "}") } else { checkUnique(c.universe)(tpeMsgT, tpeMyChannels) foreach (c.error(c.enclosingPosition, _)) - val channels = toChannels(c.universe)(replyChannels(c.universe)(tpeMyChannels, tpeMsgT)) + // need to calculate the intersection of the reply channel sets for all input channels + val intersection = inputChannels(c.universe)(tpeMsgT) map (replyChannels(c.universe)(tpeMyChannels, _).toSet) reduce (_ intersect _) + val channels = toChannels(c.universe)(intersection.toList) implicit val ttMyChannels = c.TypeTag[MyChannels](tpeMyChannels) implicit val ttReplyChannels = c.TypeTag[ReplyChannels](channels) implicit val ttMsgT = c.TypeTag[MsgT](tpeMsgT) @@ -40,7 +42,10 @@ object Channel { c.prefix.splice.channelListTypeTag = universe.typeTag[MyChannels]) if (tpeMsgT <:< typeOf[ChannelList]) { implicit val ttMsgTChan = c.TypeTag[MsgTChan](tpeMsgT) // this is MsgT reinterpreted as <: ChannelList - implicit val ttLUB = c.TypeTag[LUB](c.universe.lub(inputChannels(c.universe)(tpeMsgT))) + implicit val ttLUB = inputChannels(c.universe)(tpeMsgT) match { + case x :: Nil ⇒ c.TypeTag[LUB](typeOf[Any]) + case xs ⇒ c.TypeTag[LUB](c.universe.lub(xs)) + } reify { prepTree.splice c.prefix.splice.behaviorist[(WrappedMessage[MsgTChan, LUB], ChannelRef[ReplyChannels]) ⇒ Unit, MsgT]( diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala index d2c79b381e..008a08ca30 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -77,6 +77,9 @@ object Helpers { */ final def replyChannels(u: Universe)(list: u.Type, msg: u.Type): List[u.Type] = { import u._ + val imp = u.mkImporter(ru) + val tpeReplyTypes = imp.importType(ru.typeOf[ReplyChannels[_]]) + val tpeTNil = imp.importType(ru.typeOf[TNil]) val msgTypes = inputChannels(u)(msg) def rec(l: Type, acc: List[Type]): List[Type] = { l match { @@ -84,7 +87,11 @@ object Helpers { rec(tail, if (acc contains out) acc else out :: acc) case TypeRef(_, _, _ :: tail :: Nil) ⇒ rec(tail, acc) - case _ ⇒ acc.reverse + case x if x =:= tpeTNil ⇒ acc.reverse + case x if x <:< tpeReplyTypes ⇒ throw new IllegalArgumentException("cannot compute the ReplyChannels of a ReplyChannels type") + case x @ TypeRef(NoPrefix, _, Nil) ⇒ + acc reverse_::: (if (msgTypes exists (_ <:< x)) appliedType(tpeReplyTypes.typeConstructor, List(x)) :: Nil else Nil) + case x ⇒ throw new IllegalArgumentException(s"no idea what this type is: $x") } } val n = typeOf[Nothing] From 6e6e7f7e55aa2d422cb5723c6fbe97801968a48d Mon Sep 17 00:00:00 2001 From: Roland Date: Tue, 29 Jan 2013 22:35:45 +0100 Subject: [PATCH 19/26] first cut of the docs for typed channels --- .../scala/akka/channels/ChannelSpec.scala | 2 +- .../code/docs/channels/ChannelDocSpec.scala | 166 ++++++ akka-docs/rst/scala/index.rst | 1 + akka-docs/rst/scala/typed-channels.rst | 471 ++++++++++++++++++ .../transport/netty/NettyTransport.scala | 2 +- project/AkkaBuild.scala | 6 +- 6 files changed, 643 insertions(+), 5 deletions(-) create mode 100644 akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala create mode 100644 akka-docs/rst/scala/typed-channels.rst diff --git a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index fbc7df33bf..c7828e50a6 100644 --- a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -1,5 +1,5 @@ /** - * Copyright (C) 2009-2012 Typesafe Inc. + * Copyright (C) 2009-2013 Typesafe Inc. */ package akka.channels diff --git a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala new file mode 100644 index 0000000000..8d1553a72d --- /dev/null +++ b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala @@ -0,0 +1,166 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + +package docs.channels + +import akka.testkit.AkkaSpec +import akka.channels._ +import akka.actor.Actor +import scala.concurrent.forkjoin.ThreadLocalRandom +import scala.concurrent.Future +import scala.concurrent.duration._ +import akka.util.Timeout +import akka.testkit.TestProbe + +class ChannelDocSpec extends AkkaSpec { + + //#motivation0 + trait Request + case class Command(msg: String) extends Request + + trait Reply + case object CommandSuccess extends Reply + case class CommandFailure(msg: String) extends Reply + //#motivation0 + + class A + class B + class C + class D + + "demonstrate why Typed Channels" in { + def someActor = testActor + //#motivation1 + val requestProcessor = someActor + requestProcessor ! Command + //#motivation1 + expectMsg(Command) + + /* + //#motivation2 + val requestProcessor = new ChannelRef[(Request, Reply) :+: TNil](someActor) + requestProcessor <-!- Command // this does not compile + //#motivation2 + */ + + type Example = // + //#motivation-types + (A, B) :+: (C, D) :+: TNil + //#motivation-types + } + + // only compile test + "demonstrate channels creation" ignore { + //#declaring-channels + class AC extends Actor with Channels[TNil, (Request, Reply) :+: TNil] { + channel[Request] { (req, snd) ⇒ + req match { + case Command("ping") ⇒ snd <-!- CommandSuccess + case _ ⇒ + } + } + } + //#declaring-channels + + //#declaring-subchannels + class ACSub extends Actor with Channels[TNil, (Request, Reply) :+: TNil] { + channel[Command] { (cmd, snd) ⇒ snd <-!- CommandSuccess } + channel[Request] { (req, snd) ⇒ + if (ThreadLocalRandom.current.nextBoolean) snd <-!- CommandSuccess + else snd <-!- CommandFailure("no luck") + } + } + //#declaring-subchannels + } + + // only compile test + "demonstrating message sending" ignore { + import scala.concurrent.ExecutionContext.Implicits.global + //#sending + implicit val dummySender: ChannelRef[(Any, Nothing) :+: TNil] = ??? + implicit val timeout: Timeout = ??? // for the ask operations + + val channelA: ChannelRef[(A, B) :+: TNil] = ??? + val channelB: ChannelRef[(B, C) :+: TNil] = ??? + val channelC: ChannelRef[(C, D) :+: TNil] = ??? + + val a = new A + val fA = Future { new A } + + channelA <-!- a // send a to channelA + a -!-> channelA // same thing as above + + //channelA <-!- fA // eventually send the future’s value to channelA + fA -!-> channelA // same thing as above + + // ask the actor; return type given in full for illustration + val fB: Future[WrappedMessage[(B, Nothing) :+: TNil, B]] = channelA <-?- a + val fBunwrapped: Future[B] = fB.lub + + a -?-> channelA // same thing as above + + //channelA <-?- fA // eventually ask the actor, return the future + fA -?-> channelA // same thing as above + + // chaining works as well + a -?-> channelA -?-> channelB -!-> channelC + //#sending + } + + "demonstrate message forwarding" in { + //#forwarding + import scala.reflect.runtime.universe.TypeTag + + class Latch[T1: TypeTag, T2: TypeTag](target: ChannelRef[(T1, T2) :+: TNil]) + extends Actor with Channels[TNil, (Request, Reply) :+: (T1, T2) :+: TNil] { + + implicit val timeout = Timeout(5.seconds) + + //#become + channel[Request] { + + case (Command("close"), snd) ⇒ + channel[T1] { (t, s) ⇒ t -?-> target -!-> s } + snd <-!- CommandSuccess + + case (Command("open"), snd) ⇒ + channel[T1] { (_, _) ⇒ } + snd <-!- CommandSuccess + } + + //#become + channel[T1] { (t, snd) ⇒ t -?-> target -!-> snd } + } + //#forwarding + + val probe = TestProbe() + val _target = new ChannelRef[(String, Int) :+: TNil](probe.ref) + val _self = new ChannelRef[(Any, Nothing) :+: TNil](testActor) + //#usage + implicit val selfChannel: ChannelRef[(Any, Nothing) :+: TNil] = _self + val target: ChannelRef[(String, Int) :+: TNil] = _target // some actor + + // type given just for demonstration purposes + val latch: ChannelRef[(Request, Reply) :+: (String, Int) :+: TNil] = + ChannelExt(system).actorOf(new Latch(target)) + + "hello" -!-> latch + //#processing + probe.expectMsg("hello") + probe.reply(5) + //#processing + expectMsg(5) // this is a TestKit-based example + + Command("open") -!-> latch + expectMsg(CommandSuccess) + + "world" -!-> latch + //#processing + probe.expectNoMsg(500.millis) + //#processing + expectNoMsg(500.millis) + //#usage + } + +} \ No newline at end of file diff --git a/akka-docs/rst/scala/index.rst b/akka-docs/rst/scala/index.rst index ddceb9fcf8..c0ad9fe985 100644 --- a/akka-docs/rst/scala/index.rst +++ b/akka-docs/rst/scala/index.rst @@ -8,6 +8,7 @@ Scala API actors typed-actors + typed-channels logging event-bus scheduler diff --git a/akka-docs/rst/scala/typed-channels.rst b/akka-docs/rst/scala/typed-channels.rst new file mode 100644 index 0000000000..d998c216ca --- /dev/null +++ b/akka-docs/rst/scala/typed-channels.rst @@ -0,0 +1,471 @@ +.. _typed-channels: + +############## +Typed Channels +############## + +Motivation +========== + +Actors derive great strength from their strong encapsulation, which enables +internal restarts as well as changing behavior and also composition. The last +one is enabled by being able to inject an actor into a message exchange +transparently, because all either side ever sees is an :class:`ActorRef`. The +straight-forward way to implement this encapsulation is to keep the actor +references untyped, and before the advent of macros in Scala 2.10 this was the +only tractable way. + +As a motivation for change consider the following simple example: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation0 + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation1 + +This is an error which is quite common, and the reason is that the compiler +does not catch it and cannot warn about it. Now if there were some type +restrictions on which messages the ``commandProcessor`` can process, that would +be a different story: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation2 + +The :class:`ChannelRef` wraps a normal untyped :class:`ActorRef`, but it +expresses a type constraint, namely that this channel accepts only messages of +type :class:`Request`, to which it may reply with messages of type +:class:`Reply`. The types do not express any guarantees on how many messages +will be exchanged, whether they will be received or processed, or whether a +reply will actually be sent. They only restrict those actions which are known +to be doomed already at compile time. In this case the second line would flag +an error, since the companion object ``Command`` is not an instance of type +:class:`Request`. + +While this example looks pretty simple, the implications are profound. In order +to be useful, the system must be as reliable as you would expect a type system +to be. This means that unless you step outside of the it (i.e. doing the +equivalent of ``.asInstanceOf[_]``) you shall be protected, failures shall be +recognized and flagged. There are a number of challenges included in this +requirement, which are discussed in the following sections. If you are reading +this chapter for the first time and are not currently interested in exactly why +things are as they are, you may skip ahead to `Terminology`_. + +The Type Pollution Problem +-------------------------- + +What if an actor accepts two different types of messages? It might be a main +communications channel which is forwarded to worker actors for performing some +long-running and/or dangerous task, plus an administrative channel for the +routing of requests. Or it might be a generic message throttler which accepts a +generic channel for passing it through (which delay where appropriate) and a +management channel for setting the throttling rate. In the second case it is +especially easy to see that those two channels will probably not be related, +their types will not be derived from a meaningful common supertype; instead the +least upper bound will probably be :class:`AnyRef`. If a typed channel +reference only had the capability to express a single type, this type would +then be no restriction anymore. + +One solution to this is to never expose references describe more than one +channel at a time. But where would these references come from? It would be very +difficult to make this construction process type-safe, and it would also be an +inconvenient restriction, since message ordering guarantees only apply for the +same sender–receive pair, and if there are relations between the messages sent +on multiple channels those would need more boilerplate code to realize than if +all interaction were possible through a single reference. + +The other solution thus is to express multiple channel types by a single +channel reference, which requires the implementation of type lists and +computations on these. And as we will see below it also requires the +specification of possibly multiple reply channels per input type, hence a type +map. The implementation chosen uses type lists like this: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation-types + +This type expresses two channels: type ``A`` may stimulate replies of type +``B``, while type ``C`` may evoke replies of type ``D``. The type operator +``:+:`` is a binary type which form a list of these channel definitions, and +like every good list it ends with an empty terminator ``TNil``. + +The Reply Problem +----------------- + +Akka actors have the power to reply to any message they receive, which is also +a message send and shall also be covered by typed channels. Since the sending +actor is the one which will also receive the reply, this needs to be verified. +The solution to this problem is that in addition to the ``self`` reference, +which is implicitly picked up as the sender for untyped actor interactions, +there is also a ``selfChannel`` which describes the typed channels handled by +this actor. Thus at the call site of the message send it must be verified that +this actor can actually handle the reply for that given message send. + +The Sender Ping-Pong Problem +---------------------------- + +After successfully sending a message to an actor over a typed channel, that +actor will have a reference to the message’s sender, because normal Akka +message processing rules apply. For this sender reference there must exist a +typed channel reference which describes the possible reply types which are +applicable for each of the incoming message channels. We will see below how +this reference is provided in the code, the problem we want to highlight here +is a different one: the nature of any sender reference is that it is highly +dynamic, the compiler cannot possibly know who sent the message we are +currently processing. + +But this does not mean that all hope is lost: the solution is to do *all* +type-checking at the call site of the message send. The receiving actor just +needs to declare its channel descriptions in its own type, and channel +references are derived at construction from this type (implying the existence +of a typed ``actorOf``). Then the actor knows for each received message type +which the allowed reply types are. The typed channel for the sender reference +hence has the reply types for the current input channel as its own input types, +but what should the reply types be? This is the ping-pong problem: + +* ActorA sends MsgA to ActorB + +* ActorB replies with MsgB + +* ActorA replies with MsgC + +Every “reply” uses the sender channel, which is dynamic and hence only known +partially. But ActorB did not know who sent the message it just replied to and +hence it cannot check that it can process the possible replies following that +message send. Only ActorA could have known, because it knows its own channels +as well as ActorB’s channels completely. The solution is thus to recursively +verify the message send, following all reply channels until all possible +message types to be sent have been verified. This sounds horribly complex, but +the algorithm for doing so actually has a worst-case complexity of O(N) where N +is the number of input channels of ActorA or ActorB, whoever has fewer. + +The Parent Problem +------------------ + +There is one other actor reference which is available to ever actor: its +parent. Since the child–parent relationship is established permanently when the +child is created by the parent, this problem is easily solvable by encoding the +requirements of the child for its parent channel in its type signature having +the typed variant of ``actorOf`` verify this against the ``selfChannel``. + +Anecdotally, since the guardian actor does not care at all about message sent +to it, top-level type channel actors must declare their parent channel to be +empty. + +The Exposure/Restriction Problem +-------------------------------- + +An actor may provide more than one service, either itself or by proxy, each +with their own set of channels. Only having references for the full set of +channels leads to a too wide spread of capabilities: in the example of the +message rate throttling actor its management channel is only meant to be used +by the actor which inserted it, not by the two actors between it was inserted. +Hence the manager will have to create a channel reference which excludes the +management channels before handing out the reference to other actors. + +Another variant of this problem is an actor which handles a channel whose input +type is a supertype for a number of derived channels. It should be allowed to +use the “superchannel” in place of any of the subchannels, but not the other +way around. The intuitive approach would be to model this by making the channel +reference contravariant in its channel types and define those channel types +accordingly. This does not work nicely, however, because Scala’s type system is +not well-suited to modeling such calculations on unordered type lists; it might +be possible but its implementation would be forbiddingly complex. + +Therefore this topic gained traction as macros became available: being able to +write down type calculations using standard collections and their +transformations reduces the implementation to a handful of lines. The “narrow” +operation implemented this way allows all narrowing of input channels and +widening of output channels down to ``(Nothing, Any)`` (which is to say: +removal). + +The Forwarding Problem +---------------------- + +One important feature of actors mentioned above is their composability which is +enabled by being able to forward or delegate messages. It is the nature of this +process that the sending party is not aware of the true destination of the +message, it only sees the façade in front of it. Above we have seen that the +sender ping-pong problem requires all verification to be performed at the +sender’s end, but if the sender does not know the final recipient, how can it +check that the message exchange is type-safe? + +The forwarding party—the middle-man—is also not in the position to make this +call, since all it has is the incomplete sender channel which is lacking reply +type information. The problem which arises lies precisely in these reply +sequences: the ping-pong scheme was verified against the middle-man, and if the +final recipient would reply to the forwarded request, that sender reference +would belong to a different channel and there is no single location in the +source code where all these pieces are known at compile time. + +The solution to this problem is not to allow forwarding in the normal untyped +:class:`ActorRef` sense. Replies must always be sent by the recipient of the +original message in order for the type checks at the sender site to be +effective. Since forwarding is an important communication pattern among actors, +support for it is thus provided in the form of the :meth:`ask` pattern combined +with the :meth:`pipe` pattern, which both are not add-ons but fully integrated +operations among typed channels. + +The JVM Erasure Problem +----------------------- + +When an actor with typed channels receives a message, this message needs to be +dispatched internally to the right channel, so that the right sender channel +can be presented and so on. This dispatch needs to work with the information +contained in the message, which due to the erasure of generic type information +is an incomplete image of the true channel types. Those full types exist only +at compile-time and reifying them into TypeTags at runtime for every message +send would be prohibitively expensive. This means that channels which erase to +the same JVM type cannot coexist within the same actor, message would not be +routable reliably in that case. + +The Actor Lookup Problem +------------------------ + +Everything up to this point has assumed that channel references are passed from +their point of creation to their point of use directly and in the regime of +strong, unerased types. This can also happen between actors by embedding them +in case classes with proper type information. But one particular useful feature +of Akka actors is that they have a stable identity by which they can be found, +a unique name. This name is represented as a :class:`String` and naturally does +not bear any type information concerning the actor’s channels. Thus, when +looking up an actor with ``system.actorFor(...)`` you will only get an untyped +:class:`ActorRef` and not a channel reference. This :class:`ActorRef` can of +course manually be wrapped in a channel reference bearing the desired channels, +but this is not a type-safe operation. + +The solution in this case must be a runtime check. There is an operation to +“narrow” an :class:`ActorRef` to a channel reference of given type, which +behind the scenes will send a message to the designated actor with a TypeTag +representing the requested channels. The actor will check these against its own +TypeTag and reply with the verification result. This check uses the same code +as the compile-time “narrow” operation introduced above. + +Terminology +=========== + +.. describe:: type Channel[I, O] = (I, O) + + A Channel is a pair of an input type and and output type. The input type is + the type of message accepted by the channel, the output type is the possible + reply type and may be ``Nothing`` to signify that no reply is sent. The input + type cannot be ``Nothing``. + +.. describe:: type ChannelList + + A ChannelList is an ordered collection of Channels, without further + restriction on the input or output types of these. This means that a single + input type may be associated with multiple output types within the same + ChannelList. + +.. describe:: type TNil <: ChannelList + + The empty ChannelList. + +.. describe:: type :+:[Channel, ChannelList] <: ChannelList + + This binary type constructor is used to build up lists of Channels, for which + infix notation will be most convenient: + + .. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation-types + +.. describe:: class ChannelRef[T <: ChannelList] + + A ChannelRef is what is referred to above as the channel reference, it bears + the ChannelList which describes all input and output types and their relation + for the referenced actor. It also contains the underlying :class:`ActorRef`. + +.. describe:: trait Channels[P <: ChannelList, C <: ChannelList] + + A mixin for the :class:`Actor` trait which is parameterized in the channel + requirements this actor has for its parent (P) and its selfChannel (C). + +.. describe:: selfChannel + + An ``Actor with Channels[P, C]`` has a ``selfChannel`` of type + ``ChannelRef[C]``. This is the same type of channel reference which is + obtained by creating an instance of this actor. + +.. describe:: type ReplyChannels[T <: ChannelList] <: ChannelList + + Within an ``Actor with Channels[_, _]`` which takes a fully generic channel, + i.e. a type argument ``T <: ChannelList`` which is part of its selfChannel + type, this channel’s reply types are not known. The definition of this + channel uses the ReplyChannels type to abstractly refer to this unknown set + of channels in order to forward a reply from a ``ChannelRef[T]`` back to the + original sender. This operation’s type-safety is ensured at the sender’s site + by way of the ping-pong analysis described above. + +.. describe:: class WrappedMessage[T <: ChannelList, LUB] + + Scala’s type system cannot directly express type unions. Asking an actor with + a given input type may result in multiple possible reply types, hence the + :class:`Future` holding this reply will contain the value wrapped inside a + container which carries this type (only at compile-time). The type parameter + LUB is the least upper bound of all input channels contained in the + ChannelList T. + +Sending Messages across Channels +================================ + +Sending messages is best demonstrated in a quick overview of the basic operations: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#sending + +The first line is included so that the code compiles, since all message sends +including ``!`` will check the implicitly found selfChannel for compatibility +with the target channel’s reply types. In this case we want to demonstrate just +the syntax of sending, hence the dummy sender which accepts everything and +replies never. + +Presupposing three channel references of chainable types, an input value ``a`` +and a Future holding such a value, we demonstrate the two basic operations +which are well known from untyped actors: tell/! and ask/?. The type of the +Future returned by the ask operation may seem surprising at first, but as the +last line demonstrates, it is built in a way which makes building actor chains +very simple. What the last line does is the following: + +* it asks channelA, which returns a Future + +* a callback is installed on the Future which will use the reply value of + channelA and ask channelB with it, returning another Future + +* a callback is installed on that Future to send the reply value of channelB to + channelC, returning a Future with that previously sent value (using ``andThen``) + +This example also motivates the introduction of the “turned-around” syntax +where messages flow more naturally from left to right, instead of the standard +object-oriented view of having the tell method operate on the ActorRef given to +the left. + +This example informally introduced what is more precisely specified in the +following subsection. + +The Rules +--------- + +Operations on typed channels are composable and obey a few simple rules: + +* the message to be sent can be one of three things: + + * a :class:`Future[_]`, in which case the contained value will be sent once + available; the value will be unwrapped if it is a :class:`WrappedMessage[_, _]` + + * a :class:`WrappedMessage[_, _]`, which will be unwrapped (i.e. only the + value is sent) + + * everything else is sent as is + +* the operators are fully symmetric, i.e. ``-!->`` and ``<-!-`` do the same + thing provided the arguments also switch places + +* sending with ``-?->`` or ``<-?-`` always returns a + ``Future[WrappedMessage[_, _]]`` representing all possible reply channels, + even if there is only one (use ``.lub`` to get a :class:`Future[_]` with the + most precise single type for the value) + +* sending a :class:`Future[_]` with ``-!->`` or ``<-!-`` returns a new + :class:`Future[_]` which will be completed with the value after it has been + sent; sending a strict value returns that value + +Declaring an Actor with Channels +================================ + +The declaration of an Actor with Channels is done like this: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#declaring-channels + +It should be noted that it is impossible to declare channels which are not part +of the channel list given as the second type argument to the :class:`Channels` +trait. It is also checked—albeit at runtime—that when the actor’s construction +is complete (i.e. its constructor and ``preStart`` hook have run) every channel +listed in the selfChannel type parameter has been declared. This can in general +not be done at compile time, both due to the possibility of overriding +subclasses as well as the problem that the compiler cannot determine whether a +``channel[]`` statement will be called in the course of execution due to +external inputs (e.g. if conditionally executed). + +It should also be noted that the type of ``req`` in this example is +``Request``, hence it would be a compile-time error to try to match against the +``Command`` companion object. The ``snd`` reference is the sender channel +reference, which in this example is of type +``ChannelRef[(Reply, UnknownDoNotWriteMeDown) :+: TNil]``, meaning that sending +back a reply which is not of type ``Reply`` would be a compile-time error. + +The last thing to note is that an actor is not obliged to reply to an incoming +message, even if that was successfully delivered to it: it might not be +appropriate, or it might be impossible, the actor might have failed before +executing the replying message send, etc. And as always, the ``snd`` reference +may be used more than once, and even stored away for later. It must not leave +the actor within it was created, however, because that would defeat the +ping-pong check; this is the reason for the curious name of the fabricated +reply type ``UnknownDoNotWriteMeDown``, if you find yourself declaring that +type as part of a message or similar you know that you are cheating. + +Declaration of Subchannels +-------------------------- + +It can be convenient to carve out subchannels for special treatment like so: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#declaring-subchannels + +This means that all ``Command`` requests will be positively answered while all +others may or may not be lucky. This dispatching between the two declarations +does not depend on their order but is solely done based on which type is more +specific. + +Forwarding Messages +------------------- + +Forwarding messages has been hinted at in the last sample already, but here is +a more complete sample actor: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#forwarding + :exclude: become + +This actor declares a single-Channel parametric type which it forwards to a +target actor, handing replies back to the original sender using the ask/pipe +pattern. + +.. note:: + + It is important not to forget the ``TypeTag`` context bound for all type + arguments which are used in channel declarations, otherwise the not very + helpful error “Predef is not an enclosing class” will haunt you. + +Changing Behavior at Runtime +---------------------------- + +The actor from the previous example gets a lot more interesting when +implementing its control channel: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#forwarding + +This shows all elements of the toolkit in action: calling ``channel[T1]`` again +during the lifetime of the actor will alter its behavior on that channel. In +this case a latch or gate is modeled which when closed will permit the message +flow through and when not will drop the messages to the floor. + +Creating Actors with Channels +----------------------------- + +Creating top-level actors with channels is done using the ``ChannelExt`` extension: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala + :include: usage + :exclude: processing + +Implementation Restrictions +--------------------------- + +The erasure-based dispatch of incoming messages requires all channels which are +declared to have unique JVM type representations, i.e. it is not possible to +have two channel declarations with types ``List[A]`` and ``List[B]`` because +both would at runtime only be known as ``List[_]``. + +The specific dispatch mechanism also require the declaration of all channels or +subchannels during the actor’s construction, independent of whether they shall +later change behavior or not. Changing behavior for a subchannel is only +possible if that subchannel was declared up-front. + +TypeTags are currently (Scala 2.10.0) not serializable, hence narrowing of +:class:`ActorRef` does not work for remote references. + +How to read The Types +===================== + + diff --git a/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala b/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala index 2f10dfb4f7..d600fbc2c9 100644 --- a/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala +++ b/akka-remote/src/main/scala/akka/remote/transport/netty/NettyTransport.scala @@ -340,7 +340,7 @@ class NettyTransport(private val settings: NettyTransportSettings, private val s Future.failed(e) }) onFailure { case t: ConnectException ⇒ statusPromise failure new NettyTransportException(t.getMessage, t.getCause) - case t ⇒ statusPromise failure t + case t ⇒ statusPromise failure t } statusPromise.future diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index c3e5c3fc38..7ea99e6b5a 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -364,7 +364,7 @@ object AkkaBuild extends Build { lazy val docs = Project( id = "akka-docs", base = file("akka-docs"), - dependencies = Seq(actor, testkit % "test->test", mailboxesCommon % "compile;test->test", + dependencies = Seq(actor, testkit % "test->test", mailboxesCommon % "compile;test->test", channels, remote, cluster, slf4j, agent, dataflow, transactor, fileMailbox, zeroMQ, camel, osgi, osgiAries), settings = defaultSettings ++ site.settings ++ site.sphinxSupport() ++ site.publishSite ++ sphinxPreprocessing ++ cpsPlugin ++ Seq( sourceDirectory in Sphinx <<= baseDirectory / "rst", @@ -720,10 +720,10 @@ object Dependencies { val osgiCore = "org.osgi" % "org.osgi.core" % "4.2.0" // ApacheV2 // Camel Sample - val camelJetty = "org.apache.camel" % "camel-jetty" % camelCore.revision // ApacheV2 + val camelJetty = "org.apache.camel" % "camel-jetty" % camelCore.revision // ApacheV2 // Cluster Sample - val sigar = "org.hyperic" % "sigar" % "1.6.4" // ApacheV2 + val sigar = "org.hyperic" % "sigar" % "1.6.4" // ApacheV2 // Test From aec29618e3b0e843ce67d879d863d1e0cfcfa1fd Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 31 Jan 2013 08:49:12 +0100 Subject: [PATCH 20/26] =?UTF-8?q?require=20=E2=80=9CActor=20with=20Channel?= =?UTF-8?q?s=E2=80=9D=20by=20using=20self-typing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scala/akka/channels/ChannelSpec.scala | 58 ++++++++++--------- .../akka/channels/ChannelExtension.scala | 3 +- .../main/scala/akka/channels/Channels.scala | 4 +- .../akka/channels/macros/CreateChild.scala | 5 +- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index c7828e50a6..088dceeeca 100644 --- a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -43,19 +43,19 @@ object ChannelSpec { object D extends D // used for sender verification in the first two test cases - class Tester extends Channels[TNil, (A, C) :+: (B, D) :+: TNil] { + class Tester extends Actor with Channels[TNil, (A, C) :+: (B, D) :+: TNil] { channel[A.type] { (msg, snd) ⇒ snd <-!- C } channel[A] { (msg, snd) ⇒ snd <-!- C1 } channel[B] { case (B, s) ⇒ s <-!- D } } - class RecvC(ref: ActorRef) extends Channels[TNil, (C, Nothing) :+: TNil] { + class RecvC(ref: ActorRef) extends Actor with Channels[TNil, (C, Nothing) :+: TNil] { channel[C] { case (x, _) ⇒ ref ! x } } // pos compile test for multiple reply channels - class SubChannels extends Channels[TNil, (A, B) :+: (A, C) :+: TNil] { + class SubChannels extends Actor with Channels[TNil, (A, B) :+: (A, C) :+: TNil] { channel[A] { case (A1, x) ⇒ B -!-> x case (_, x) ⇒ x <-!- C @@ -63,8 +63,8 @@ object ChannelSpec { } // pos compile test for children - class Children extends Channels[TNil, (A, B) :+: (C, Nothing) :+: TNil] { - val c = createChild(new Channels[(A, Nothing) :+: TNil, (B, C) :+: TNil] { + class Children extends Actor with Channels[TNil, (A, B) :+: (C, Nothing) :+: TNil] { + val c = createChild(new Actor with Channels[(A, Nothing) :+: TNil, (B, C) :+: TNil] { channel[B] { case (B, s) ⇒ s <-!- C } }) @@ -76,12 +76,12 @@ object ChannelSpec { case (C, _) ⇒ client ! C } - createChild(new Channels[(C, Nothing) :+: TNil, TNil] {}) - createChild(new Channels[(A, Nothing) :+:(C, Nothing) :+: TNil, TNil] {}) + createChild(new Actor with Channels[(C, Nothing) :+: TNil, TNil] {}) + createChild(new Actor with Channels[(A, Nothing) :+:(C, Nothing) :+: TNil, TNil] {}) } // compile test for polymorphic actors - class WriteOnly[T1: ru.TypeTag, T2: ru.TypeTag](target: ChannelRef[(T1, T2) :+: TNil]) extends Channels[TNil, (D, D) :+: (T1, T2) :+: TNil] { + class WriteOnly[T1: ru.TypeTag, T2: ru.TypeTag](target: ChannelRef[(T1, T2) :+: TNil]) extends Actor with Channels[TNil, (D, D) :+: (T1, T2) :+: TNil] { implicit val t = Timeout(1.second) import akka.pattern.ask @@ -91,7 +91,7 @@ object ChannelSpec { } // compile test for whole-channel polymorphism - class Poly[T <: ChannelList: ru.TypeTag](target: ChannelRef[T]) extends Channels[TNil, (A, A) :+: (B, B) :+: T] { + class Poly[T <: ChannelList: ru.TypeTag](target: ChannelRef[T]) extends Actor with Channels[TNil, (A, A) :+: (B, B) :+: T] { implicit val timeout = Timeout(1.second) channel[T] { (x, snd) ⇒ val xx: WrappedMessage[T, Any] = x @@ -107,11 +107,11 @@ object ChannelSpec { } // companion to WriteOnly for testing pass-through - class EchoTee(target: ActorRef) extends Channels[TNil, (C, C) :+: TNil] { + class EchoTee(target: ActorRef) extends Actor with Channels[TNil, (C, C) :+: TNil] { channel[C] { (c, snd) ⇒ target ! C1; snd <-!- C1 } } - class MissingChannel extends Channels[TNil, (A, A) :+: (B, B) :+: TNil] { + class MissingChannel extends Actor with Channels[TNil, (A, A) :+: (B, B) :+: TNil] { channel[A.type] { (_, _) ⇒ } } @@ -123,7 +123,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, implicit val selfChannel = new ChannelRef[(Any, Nothing) :+: TNil](testActor) - "Channels" must { + "Actor with Channels" must { "construct refs" in { val ref = ChannelExt(system).actorOf(new Tester) @@ -231,7 +231,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (A, B) :+: TNil] { + |new Actor with Channels[TNil, (A, B) :+: TNil] { | channel[B] { | case (B, _) => | } @@ -245,7 +245,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (A, B) :+: (A1.type, C) :+: TNil] { + |new Actor with Channels[TNil, (A, B) :+: (A1.type, C) :+: TNil] { | channel[A] { | case (A1, x) => x <-!- C | } @@ -258,9 +258,10 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, intercept[ToolBoxError] { eval(""" |import akka.channels._ + |import akka.actor.Actor |import ChannelSpec._ - |new Channels[TNil, (A, B) :+: (C, D) :+: TNil] { - | createChild(new Channels[Nothing, Nothing] {}) + |new Actor with Channels[TNil, (A, B) :+: (C, D) :+: TNil] { + | createChild(new Actor with Channels[Nothing, Nothing] {}) |} """.stripMargin) }.message must include("Parent argument must not be Nothing") @@ -270,9 +271,10 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, intercept[ToolBoxError] { eval(""" |import akka.channels._ + |import akka.actor.Actor |import ChannelSpec._ - |new Channels[TNil, (A, B) :+: (C, D) :+: TNil] { - | createChild(new Channels[(B, Nothing) :+: TNil, TNil] {}) + |new Actor with Channels[TNil, (A, B) :+: (C, D) :+: TNil] { + | createChild(new Actor with Channels[(B, Nothing) :+: TNil, TNil] {}) |} """.stripMargin) }.message must include("This actor cannot support a child requiring channels akka.channels.ChannelSpec.B") @@ -285,8 +287,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "have a working parentChannel" in { - val parent = ChannelExt(system).actorOf(new Channels[TNil, (A, Nothing) :+: TNil] { - createChild(new Channels[(A, Nothing) :+: TNil, TNil] { + val parent = ChannelExt(system).actorOf(new Actor with Channels[TNil, (A, Nothing) :+: TNil] { + createChild(new Actor with Channels[(A, Nothing) :+: TNil, TNil] { parentChannel <-!- A }) channel[A] { (msg, snd) ⇒ testActor ! msg } @@ -294,12 +296,12 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, expectMsg(A) } - "not permit top-level Channels which send to parent" in { + "not permit top-level Actor with Channels which send to parent" in { intercept[ToolBoxError] { eval(""" |import akka.channels._ |import ChannelSpec._ - |null.asInstanceOf[ChannelExtension].actorOf(new Channels[(A, A) :+: TNil, (A, Nothing) :+: TNil] {}) + |null.asInstanceOf[ChannelExtension].actorOf(new Actor with Channels[(A, A) :+: TNil, (A, Nothing) :+: TNil] {}) """.stripMargin) }.message must include("type mismatch") } @@ -309,8 +311,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (A, Nothing) :+: TNil] { - | createChild(new Channels[(A, Nothing) :+: TNil, TNil] { + |new Actor with Channels[TNil, (A, Nothing) :+: TNil] { + | createChild(new Actor with Channels[(A, Nothing) :+: TNil, TNil] { | parentChannel <-!- B | }) |} @@ -400,7 +402,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, lastSender must be(wrap.actorRef) } - "allow wrapping of ChannelsRefs with replies" in { + "allow wrapping of Actor with ChannelsRefs with replies" in { val probe = TestProbe() val target = ChannelExt(system).actorOf(new EchoTee(probe.ref)) val wrap = ChannelExt(system).actorOf(new WriteOnly[C, C](target)) @@ -429,7 +431,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, eval(""" |import akka.channels._ |import ChannelSpec._ - |new Channels[TNil, (List[A], A) :+: (List[B], B) :+: TNil] { + |new Actor with Channels[TNil, (List[A], A) :+: (List[B], B) :+: TNil] { | channel[List[A]] { (x, s) ⇒ } | channel[List[B]] { (x, s) ⇒ } |} @@ -458,7 +460,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "be able to forward fully generic channels" in { - val cd = ChannelExt(system).actorOf(new Channels[TNil, (C, D) :+: TNil] { + val cd = ChannelExt(system).actorOf(new Actor with Channels[TNil, (C, D) :+: TNil] { channel[C] { (x, snd) ⇒ snd <-!- D } }) val t = ChannelExt(system).actorOf(new Poly(cd)) @@ -474,7 +476,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "A WrappedMessage" must { "be sendable to a ChannelRef" in { - implicit val selfChannel = ChannelExt(system).actorOf(new Channels[TNil, (C, Nothing) :+:(D, Nothing) :+: TNil] { + implicit val selfChannel = ChannelExt(system).actorOf(new Actor with Channels[TNil, (C, Nothing) :+:(D, Nothing) :+: TNil] { channel[C] { (c, snd) ⇒ testActor ! c } channel[D] { (d, snd) ⇒ testActor ! d } }) diff --git a/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala b/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala index 3a610c8933..fa48af3779 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala @@ -11,6 +11,7 @@ import scala.reflect.runtime.universe._ import akka.actor.Props import scala.reflect.ClassTag import scala.reflect.runtime.universe +import akka.actor.Actor object ChannelExt extends ExtensionKey[ChannelExtension] @@ -19,6 +20,6 @@ class ChannelExtension(system: ExtendedActorSystem) extends Extension { // kick-start the universe (needed due to thread safety issues in runtime mirror) private val t = typeTag[(Int, Int) :+: TNil] - def actorOf[Ch <: ChannelList](factory: ⇒ Channels[TNil, Ch]): ChannelRef[Ch] = + def actorOf[Ch <: ChannelList](factory: ⇒ Actor with Channels[TNil, Ch]): ChannelRef[Ch] = new ChannelRef[Ch](system.actorOf(Props(factory))) } diff --git a/akka-channels/src/main/scala/akka/channels/Channels.scala b/akka-channels/src/main/scala/akka/channels/Channels.scala index e3bc14ccbd..3eb0943ab5 100644 --- a/akka-channels/src/main/scala/akka/channels/Channels.scala +++ b/akka-channels/src/main/scala/akka/channels/Channels.scala @@ -40,7 +40,7 @@ import akka.actor.ActorInitializationException * erased type, which may be less precise than the actual channel type; this * can lead to ClassCastExceptions if sending through the untyped ActorRef */ -trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { +trait Channels[P <: ChannelList, C <: ChannelList] { this: Actor ⇒ import macros.Helpers._ @@ -49,7 +49,7 @@ trait Channels[P <: ChannelList, C <: ChannelList] extends Actor { * actor can handle everything which the child tries to send via its * `parent` ChannelRef. */ - def createChild[Pa <: ChannelList, Ch <: ChannelList](factory: Channels[Pa, Ch]): ChannelRef[Ch] = macro macros.CreateChild.impl[C, Pa, Ch] + def createChild[Pa <: ChannelList, Ch <: ChannelList](factory: Actor with Channels[Pa, Ch]): ChannelRef[Ch] = macro macros.CreateChild.impl[C, Pa, Ch] /** * Properly typed ChannelRef for the context.parent. diff --git a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala index 1d38f4c52c..0f1d4ba7b0 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala @@ -6,14 +6,15 @@ import ru.TypeTag import scala.reflect.macros.Context import scala.reflect.api.Universe import akka.actor.Props +import akka.actor.Actor object CreateChild { import Helpers._ def impl[MyChannels <: ChannelList: c.WeakTypeTag, ParentChannels <: ChannelList: c.WeakTypeTag, ChildChannels <: ChannelList: c.WeakTypeTag]( c: Context { - type PrefixType = Channels[_, MyChannels] - })(factory: c.Expr[Channels[ParentChannels, ChildChannels]]): c.Expr[ChannelRef[ChildChannels]] = { + type PrefixType = Actor with Channels[_, MyChannels] + })(factory: c.Expr[Actor with Channels[ParentChannels, ChildChannels]]): c.Expr[ChannelRef[ChildChannels]] = { import c.universe._ From 5e763bbb38407d2fdcf6abe1b0ab770dc9377ee5 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 31 Jan 2013 09:05:49 +0100 Subject: [PATCH 21/26] =?UTF-8?q?change=20synthetic=20sender=E2=80=99s=20r?= =?UTF-8?q?eply=20type=20to=20UnknownDoNotWriteMeDown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- akka-channels/src/main/scala/akka/channels/Ops.scala | 8 ++++++++ .../src/main/scala/akka/channels/macros/Ask.scala | 6 +++--- .../src/main/scala/akka/channels/macros/Channel.scala | 2 +- .../src/main/scala/akka/channels/macros/Helpers.scala | 4 ++-- .../src/main/scala/akka/channels/macros/Tell.scala | 7 +++++-- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala index feb25c8daf..b8a3019af4 100644 --- a/akka-channels/src/main/scala/akka/channels/Ops.scala +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -14,6 +14,14 @@ sealed trait TNil extends ChannelList sealed trait :+:[A <: (_, _), B <: ChannelList] extends ChannelList sealed trait ReplyChannels[T <: ChannelList] extends ChannelList +/** + * This type is used to stand in for the unknown reply types of the fabricated + * sender references; users don’t need to write it down, and if they do, they + * know that they’re cheating (since these ref types must not escape their + * defining actor context). + */ +sealed trait UnknownDoNotWriteMeDown + class ActorRefOps(val ref: ActorRef) extends AnyVal { import macros.Helpers._ def narrow[C <: ChannelList](implicit timeout: Timeout, ec: ExecutionContext, tt: ru.TypeTag[C]): Future[ChannelRef[C]] = { diff --git a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala index e33127b78e..f4dbf475ce 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala @@ -31,7 +31,7 @@ object Ask { Tell.verify(c)(null, unwrapped, typeOf[(Any, Nothing) :+: TNil], tpeChannel) - implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out)) + implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out, weakTypeOf[Nothing])) implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) reify(askOps[WrappedMessage[ReturnChannels, ReturnLUB]]( c.prefix.splice.actorRef, toMsg(c)(msg, tpeMsg).splice)(imp[Timeout](c).splice)) @@ -54,7 +54,7 @@ object Ask { Tell.verify(c)(null, unwrapped, typeOf[(Any, Nothing) :+: TNil], tpeChannel) - implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out)) + implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out, weakTypeOf[Nothing])) implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) val msg = reify(c.prefix.splice.value) reify(askOps[WrappedMessage[ReturnChannels, ReturnLUB]]( @@ -78,7 +78,7 @@ object Ask { Tell.verify(c)(null, unwrapped, typeOf[(Any, Nothing) :+: TNil], tpeChannel) - implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out)) + implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out, weakTypeOf[Nothing])) implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) if (tpeMsg <:< typeOf[ChannelList]) reify(askFutureWrapped[WrappedMessage[ReturnChannels, ReturnLUB]]( diff --git a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala index 8c93577c74..d39f5ce201 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala @@ -34,7 +34,7 @@ object Channel { checkUnique(c.universe)(tpeMsgT, tpeMyChannels) foreach (c.error(c.enclosingPosition, _)) // need to calculate the intersection of the reply channel sets for all input channels val intersection = inputChannels(c.universe)(tpeMsgT) map (replyChannels(c.universe)(tpeMyChannels, _).toSet) reduce (_ intersect _) - val channels = toChannels(c.universe)(intersection.toList) + val channels = toChannels(c.universe)(intersection.toList, weakTypeOf[UnknownDoNotWriteMeDown]) implicit val ttMyChannels = c.TypeTag[MyChannels](tpeMyChannels) implicit val ttReplyChannels = c.TypeTag[ReplyChannels](channels) implicit val ttMsgT = c.TypeTag[MsgT](tpeMsgT) diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala index 008a08ca30..9e61e5f297 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -117,7 +117,7 @@ object Helpers { * convert a list of types List(, , ...) into a ChannelList * ( Channel[, Nothing] :=: Channel[, Nothing] :=: ... :=: TNil ) */ - final def toChannels(u: Universe)(list: List[u.Type]): u.Type = { + final def toChannels(u: Universe)(list: List[u.Type], out: u.Type): u.Type = { import u._ def rec(l: List[Type], acc: Type): Type = l match { case head :: (tail: List[Type]) ⇒ @@ -127,7 +127,7 @@ object Helpers { appliedType(weakTypeOf[:+:[_, _]].typeConstructor, List( appliedType(weakTypeOf[Tuple2[_, _]].typeConstructor, List( head, - weakTypeOf[Nothing])), + out)), acc))) case _ ⇒ acc } diff --git a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala index 1686a9b782..c86f7e5d3c 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala @@ -70,15 +70,18 @@ object Tell { } def verify(c: Context)(sender: c.universe.Tree, msgT: c.universe.Type, sndT: c.universe.Type, chT: c.universe.Type)(): Unit = { + val unknown = c.universe.weakTypeOf[UnknownDoNotWriteMeDown] + val nothing = c.universe.weakTypeOf[Nothing] + def ignoreUnknown(in: c.universe.Type): c.universe.Type = if (in =:= unknown) nothing else in def rec(msg: Set[c.universe.Type], checked: Set[c.universe.Type], depth: Int): Unit = if (msg.nonEmpty) { val u: c.universe.type = c.universe - val replies = msg map (m ⇒ m -> replyChannels(u)(chT, m)) + val replies = msg map (m ⇒ m -> (replyChannels(u)(chT, m) map (t => ignoreUnknown(t)))) val missing = replies collect { case (k, v) if v.size == 0 ⇒ k } if (missing.nonEmpty) error(c, s"target ChannelRef does not support messages of types ${missing mkString ", "} (at depth $depth)") else { - val nextSend = replies.map(_._2).flatten map (m ⇒ m -> replyChannels(u)(sndT, m)) + val nextSend = replies.map(_._2).flatten map (m ⇒ m -> (replyChannels(u)(sndT, m) map (t => ignoreUnknown(t)))) val nextMissing = nextSend collect { case (k, v) if v.size == 0 ⇒ k } if (nextMissing.nonEmpty) error(c, s"implicit sender `$sender` does not support messages of the reply types ${nextMissing mkString ", "} (at depth $depth)") From f86fa61613dc9e703e2b8f74f0184b0fa004461e Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 31 Jan 2013 11:12:17 +0100 Subject: [PATCH 22/26] make all arrows invertible --- .../scala/akka/channels/ChannelSpec.scala | 16 +++++ .../main/scala/akka/channels/ChannelRef.scala | 4 +- .../src/main/scala/akka/channels/Ops.scala | 6 +- .../main/scala/akka/channels/macros/Ask.scala | 23 +++++-- .../scala/akka/channels/macros/Tell.scala | 62 +++++++++++-------- .../code/docs/channels/ChannelDocSpec.scala | 4 +- 6 files changed, 78 insertions(+), 37 deletions(-) diff --git a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index 088dceeeca..a771af2197 100644 --- a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -482,7 +482,9 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, }) val t = ChannelExt(system).actorOf(new Tester) val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) + val fa = Future successful a val b = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](B) + val fb = Future successful b t <-!- a expectMsg(C) a -!-> t @@ -491,6 +493,14 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, expectMsg(D) b -!-> t expectMsg(D) + t <-!- fa + expectMsg(C) + fa -!-> t + expectMsg(C) + t <-!- fb + expectMsg(D) + fb -!-> t + expectMsg(D) } "not be sendable with wrong channels" when { @@ -540,11 +550,17 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, implicit val timeout = Timeout(1.second) val t = ChannelExt(system).actorOf(new Tester) val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) + val fa = Future successful a val b = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](B) + val fb = Future successful b (Await.result(t <-?- a, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(C) (Await.result(a -?-> t, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(C) (Await.result(t <-?- b, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(D) (Await.result(b -?-> t, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(D) + (Await.result(t <-?- fa, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(C) + (Await.result(fa -?-> t, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(C) + (Await.result(t <-?- fb, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(D) + (Await.result(fb -?-> t, timeout.duration): WrappedMessage[(C, Nothing) :+: (D, Nothing) :+: TNil, Msg]).value must be(D) } "not be askable with wrong channels" when { diff --git a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala index 34541a7b8e..5f23b6fa3f 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala @@ -21,7 +21,9 @@ case class NarrowingException(errors: String) extends AkkaException(errors) with class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { - def <-!-[M](msg: M): Unit = macro macros.Tell.impl[T, M] + def <-!-[M](msg: M): M = macro macros.Tell.impl[T, M] + + def <-!-[M](future: Future[M]): Future[M] = macro macros.Tell.futureImpl[T, M] def <-?-[M](msg: M): Future[_] = macro macros.Ask.impl[ChannelList, Any, T, M] diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala index b8a3019af4..2448f6a958 100644 --- a/akka-channels/src/main/scala/akka/channels/Ops.scala +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -17,7 +17,7 @@ sealed trait ReplyChannels[T <: ChannelList] extends ChannelList /** * This type is used to stand in for the unknown reply types of the fabricated * sender references; users don’t need to write it down, and if they do, they - * know that they’re cheating (since these ref types must not escape their + * know that they’re cheating (since these ref types must not escape their * defining actor context). */ sealed trait UnknownDoNotWriteMeDown @@ -34,7 +34,7 @@ class ActorRefOps(val ref: ActorRef) extends AnyVal { } class FutureOps[T](val future: Future[T]) extends AnyVal { - def -!->[C <: ChannelList](channel: ChannelRef[C]): Future[T] = macro macros.Tell.futureImpl[C, T] + def -!->[C <: ChannelList](channel: ChannelRef[C]): Future[T] = macro macros.Tell.futureOpsImpl[C, T] def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.futureImpl[ChannelList, Any, C, T] def lub[LUB](implicit ev: T <:< WrappedMessage[_, LUB]): Future[LUB] = { implicit val ec = ExecutionContexts.sameThreadExecutionContext @@ -43,7 +43,7 @@ class FutureOps[T](val future: Future[T]) extends AnyVal { } class AnyOps[T](val value: T) extends AnyVal { - def -!->[C <: ChannelList](channel: ChannelRef[C]): Unit = macro macros.Tell.opsImpl[C, T] + def -!->[C <: ChannelList](channel: ChannelRef[C]): T = macro macros.Tell.opsImpl[C, T] def -?->[C <: ChannelList](channel: ChannelRef[C]): Future[_] = macro macros.Ask.opsImpl[ChannelList, Any, C, T] } diff --git a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala index f4dbf475ce..267aee9115 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala @@ -26,15 +26,29 @@ object Ask { val tpeChannel = weakTypeOf[Channel] val tpeMsg = weakTypeOf[Msg] - val unwrapped = unwrapMsgType(c.universe)(tpeMsg) + val isFuture = tpeMsg <:< typeOf[Future[_]] + val unwrapped = + if (isFuture) + tpeMsg match { + case TypeRef(_, _, x :: _) ⇒ unwrapMsgType(c.universe)(x) + } + else unwrapMsgType(c.universe)(tpeMsg) val out = replyChannels(c.universe)(tpeChannel, unwrapped) Tell.verify(c)(null, unwrapped, typeOf[(Any, Nothing) :+: TNil], tpeChannel) implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out, weakTypeOf[Nothing])) implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) - reify(askOps[WrappedMessage[ReturnChannels, ReturnLUB]]( - c.prefix.splice.actorRef, toMsg(c)(msg, tpeMsg).splice)(imp[Timeout](c).splice)) + if (isFuture) + if (unwrapped <:< typeOf[ChannelList]) + reify(askFutureWrapped[WrappedMessage[ReturnChannels, ReturnLUB]]( + c.prefix.splice.actorRef, msg.splice.asInstanceOf[Future[WrappedMessage[TNil, Any]]])(imp[Timeout](c).splice)) + else + reify(askFuture[WrappedMessage[ReturnChannels, ReturnLUB]]( + c.prefix.splice.actorRef, msg.splice.asInstanceOf[Future[Any]])(imp[Timeout](c).splice)) + else + reify(askOps[WrappedMessage[ReturnChannels, ReturnLUB]]( + c.prefix.splice.actorRef, toMsg(c)(msg, tpeMsg).splice)(imp[Timeout](c).splice)) } def opsImpl[ // @@ -61,6 +75,7 @@ object Ask { channel.splice.actorRef, toMsg(c)(msg, tpeMsg).splice)(imp[Timeout](c).splice)) } + // this is the implementation for Future[_] -?-> ChannelRef[_] def futureImpl[ // ReturnChannels <: ChannelList, // the precise type union describing the reply ReturnLUB, // the least-upper bound for the reply types @@ -80,7 +95,7 @@ object Ask { implicit val ttReturnChannels = c.TypeTag[ReturnChannels](toChannels(c.universe)(out, weakTypeOf[Nothing])) implicit val ttReturnLUB = c.TypeTag[ReturnLUB](c.universe.lub(out)) - if (tpeMsg <:< typeOf[ChannelList]) + if (tpeMsg <:< typeOf[WrappedMessage[_, _]]) reify(askFutureWrapped[WrappedMessage[ReturnChannels, ReturnLUB]]( channel.splice.actorRef, c.prefix.splice.future.asInstanceOf[Future[WrappedMessage[TNil, Any]]])(imp[Timeout](c).splice)) else diff --git a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala index c86f7e5d3c..13e4019f1b 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala @@ -16,42 +16,50 @@ object Tell { def impl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { type PrefixType = ChannelRef[MyChannels] - })(msg: c.Expr[Msg]): c.Expr[Unit] = { - val tpeMyChannels = c.universe.weakTypeOf[MyChannels] - val tpeMsg = c.universe.weakTypeOf[Msg] - val (tpeSender, senderTree, sender) = getSenderChannel(c) - - verify(c)(senderTree, unwrapMsgType(c.universe)(tpeMsg), tpeSender, tpeMyChannels) - - c.universe.reify(c.prefix.splice.actorRef.tell(toMsg(c)(msg, tpeMsg).splice, sender.splice)) - } + })(msg: c.Expr[Msg]): c.Expr[Msg] = + doTell(c)(c.universe.weakTypeOf[MyChannels], c.universe.weakTypeOf[Msg], msg, c.prefix) def opsImpl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { type PrefixType = AnyOps[Msg] - })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Unit] = { - val tpeMyChannels = c.universe.weakTypeOf[MyChannels] - val tpeMsg = c.universe.weakTypeOf[Msg] + })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Msg] = { + import c.universe._ + doTell(c)(weakTypeOf[MyChannels], weakTypeOf[Msg], reify(c.prefix.splice.value), channel) + } + + def doTell[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context)( + tpeMyChannels: c.Type, tpeMsg: c.Type, msg: c.Expr[Msg], target: c.Expr[ChannelRef[MyChannels]]): c.Expr[Msg] = { val (tpeSender, senderTree, sender) = getSenderChannel(c) - verify(c)(senderTree, unwrapMsgType(c.universe)(tpeMsg), tpeSender, tpeMyChannels) + val cond = bool(c, tpeMsg <:< c.typeOf[WrappedMessage[_, _]]) + c.universe.reify { + val $m = msg.splice + target.splice.actorRef.tell(if (cond.splice) $m.asInstanceOf[WrappedMessage[TNil, Any]].value else $m, sender.splice) + $m + } + } - val msg = c.universe.reify(c.prefix.splice.value) - c.universe.reify(channel.splice.actorRef.tell(toMsg(c)(msg, tpeMsg).splice, sender.splice)) + def futureOpsImpl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { + type PrefixType = FutureOps[Msg] + })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Future[Msg]] = { + import c.universe._ + doFutureTell(c)(weakTypeOf[MyChannels], weakTypeOf[Msg], reify(c.prefix.splice.future), channel) } def futureImpl[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context { - type PrefixType = FutureOps[Msg] - })(channel: c.Expr[ChannelRef[MyChannels]]): c.Expr[Future[Msg]] = { - val tpeMyChannels = c.universe.weakTypeOf[MyChannels] - val tpeMsg = c.universe.weakTypeOf[Msg] - val (tpeSender, senderTree, sender) = getSenderChannel(c) - - verify(c)(senderTree, unwrapMsgType(c.universe)(tpeMsg), tpeSender, tpeMyChannels) - - c.universe.reify(pipeTo[Msg](c.prefix.splice, channel.splice, sender.splice)) + type PrefixType = ChannelRef[MyChannels] + })(future: c.Expr[Future[Msg]]): c.Expr[Future[Msg]] = { + import c.universe._ + doFutureTell(c)(weakTypeOf[MyChannels], weakTypeOf[Msg], future, c.prefix) } - @inline def pipeTo[Msg](f: FutureOps[Msg], c: ChannelRef[_], snd: ActorRef): Future[Msg] = + def doFutureTell[MyChannels <: ChannelList: c.WeakTypeTag, Msg: c.WeakTypeTag](c: Context)( + tpeMyChannels: c.Type, tpeMsg: c.Type, future: c.Expr[Future[Msg]], target: c.Expr[ChannelRef[MyChannels]]): c.Expr[Future[Msg]] = { + val (tpeSender, senderTree, sender) = getSenderChannel(c) + verify(c)(senderTree, unwrapMsgType(c.universe)(tpeMsg), tpeSender, tpeMyChannels) + c.universe.reify(pipeTo[Msg](future.splice, target.splice, sender.splice)) + } + + @inline def pipeTo[Msg](f: Future[Msg], c: ChannelRef[_], snd: ActorRef): Future[Msg] = f.future.andThen { case Success(s: WrappedMessage[_, _]) ⇒ c.actorRef.tell(s.value, snd) case Success(s) ⇒ c.actorRef.tell(s, snd) @@ -76,12 +84,12 @@ object Tell { def rec(msg: Set[c.universe.Type], checked: Set[c.universe.Type], depth: Int): Unit = if (msg.nonEmpty) { val u: c.universe.type = c.universe - val replies = msg map (m ⇒ m -> (replyChannels(u)(chT, m) map (t => ignoreUnknown(t)))) + val replies = msg map (m ⇒ m -> (replyChannels(u)(chT, m) map (t ⇒ ignoreUnknown(t)))) val missing = replies collect { case (k, v) if v.size == 0 ⇒ k } if (missing.nonEmpty) error(c, s"target ChannelRef does not support messages of types ${missing mkString ", "} (at depth $depth)") else { - val nextSend = replies.map(_._2).flatten map (m ⇒ m -> (replyChannels(u)(sndT, m) map (t => ignoreUnknown(t)))) + val nextSend = replies.map(_._2).flatten map (m ⇒ m -> (replyChannels(u)(sndT, m) map (t ⇒ ignoreUnknown(t)))) val nextMissing = nextSend collect { case (k, v) if v.size == 0 ⇒ k } if (nextMissing.nonEmpty) error(c, s"implicit sender `$sender` does not support messages of the reply types ${nextMissing mkString ", "} (at depth $depth)") diff --git a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala index 8d1553a72d..0806e6aad1 100644 --- a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala +++ b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala @@ -91,7 +91,7 @@ class ChannelDocSpec extends AkkaSpec { channelA <-!- a // send a to channelA a -!-> channelA // same thing as above - //channelA <-!- fA // eventually send the future’s value to channelA + channelA <-!- fA // eventually send the future’s value to channelA fA -!-> channelA // same thing as above // ask the actor; return type given in full for illustration @@ -100,7 +100,7 @@ class ChannelDocSpec extends AkkaSpec { a -?-> channelA // same thing as above - //channelA <-?- fA // eventually ask the actor, return the future + channelA <-?- fA // eventually ask the actor, return the future fA -?-> channelA // same thing as above // chaining works as well From 7ef5ace8d8643e769a2e5294c61db49f36cad163 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 31 Jan 2013 11:40:39 +0100 Subject: [PATCH 23/26] add docs on how to create child actors with channels --- .../code/docs/channels/ChannelDocSpec.scala | 57 +++++++++++++++++-- akka-docs/rst/scala/typed-channels.rst | 12 ++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala index 0806e6aad1..ef74019446 100644 --- a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala +++ b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala @@ -13,7 +13,7 @@ import scala.concurrent.duration._ import akka.util.Timeout import akka.testkit.TestProbe -class ChannelDocSpec extends AkkaSpec { +object ChannelDocSpec { //#motivation0 trait Request @@ -24,6 +24,39 @@ class ChannelDocSpec extends AkkaSpec { case class CommandFailure(msg: String) extends Reply //#motivation0 + //#child + case class Stats(b: Request) + case object GetChild + case class ChildRef(child: ChannelRef[(Request, Reply) :+: TNil]) + + class Child extends Actor + with Channels[(Stats, Nothing) :+: TNil, (Request, Reply) :+: TNil] { + + channel[Request] { (x, snd) ⇒ + parentChannel <-!- Stats(x) + snd <-!- CommandSuccess + } + } + + class Parent extends Actor + with Channels[TNil, (Stats, Nothing) :+: (GetChild.type, ChildRef) :+: TNil] { + + val child = createChild(new Child) + + channel[GetChild.type] { (_, snd) ⇒ ChildRef(child) -!-> snd } + + channel[Stats] { (x, _) ⇒ + // collect some stats + } + } + + //#child +} + +class ChannelDocSpec extends AkkaSpec { + + import ChannelDocSpec._ + class A class B class C @@ -116,14 +149,14 @@ class ChannelDocSpec extends AkkaSpec { extends Actor with Channels[TNil, (Request, Reply) :+: (T1, T2) :+: TNil] { implicit val timeout = Timeout(5.seconds) - + //#become channel[Request] { - + case (Command("close"), snd) ⇒ channel[T1] { (t, s) ⇒ t -?-> target -!-> s } snd <-!- CommandSuccess - + case (Command("open"), snd) ⇒ channel[T1] { (_, _) ⇒ } snd <-!- CommandSuccess @@ -163,4 +196,20 @@ class ChannelDocSpec extends AkkaSpec { //#usage } + "demonstrate child creation" in { + implicit val selfChannel = new ChannelRef[(Any, Nothing) :+: TNil](testActor) + //#child + // + // then it is used somewhat like this: + // + + val parent = ChannelExt(system).actorOf(new Parent) + parent <-!- GetChild + val child = expectMsgType[ChildRef].child // this assumes TestKit context + + child <-!- Command("hey there") + expectMsg(CommandSuccess) + //#child + } + } \ No newline at end of file diff --git a/akka-docs/rst/scala/typed-channels.rst b/akka-docs/rst/scala/typed-channels.rst index d998c216ca..a0b2574443 100644 --- a/akka-docs/rst/scala/typed-channels.rst +++ b/akka-docs/rst/scala/typed-channels.rst @@ -449,6 +449,18 @@ Creating top-level actors with channels is done using the ``ChannelExt`` extensi :include: usage :exclude: processing +Inside an actor with channels children are created using the ``createChild`` method: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#child + +In this example we create a simple child actor which responds to requests, but +also keeps its parent informed about what it is doing. The parent channel +within the child is thus declared to accept :class:`Stats` messages, and the +parent must consequently declare such a channel in order to be able to create +such a child. The parent’s job then is to create the child, make it available +to the outside via properly typed messages and collect the statistics coming in +from the child. + Implementation Restrictions --------------------------- From c362e8168f525c17aa78f9ee0f13732ae3a80523 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 31 Jan 2013 11:42:39 +0100 Subject: [PATCH 24/26] update copyright headers --- .../src/main/scala/akka/channels/ChannelExtension.scala | 2 +- akka-channels/src/main/scala/akka/channels/ChannelRef.scala | 2 +- akka-channels/src/main/scala/akka/channels/Channels.scala | 2 +- akka-channels/src/main/scala/akka/channels/Ops.scala | 4 ++++ akka-channels/src/main/scala/akka/channels/macros/Ask.scala | 4 ++++ .../src/main/scala/akka/channels/macros/Channel.scala | 4 ++++ .../src/main/scala/akka/channels/macros/CreateChild.scala | 4 ++++ .../src/main/scala/akka/channels/macros/Helpers.scala | 4 ++++ .../src/main/scala/akka/channels/macros/Narrow.scala | 4 ++++ akka-channels/src/main/scala/akka/channels/macros/Tell.scala | 4 ++++ akka-channels/src/main/scala/akka/channels/package.scala | 2 +- 11 files changed, 32 insertions(+), 4 deletions(-) diff --git a/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala b/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala index fa48af3779..461d0de291 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala @@ -1,5 +1,5 @@ /** - * Copyright (C) 2009-2012 Typesafe Inc. + * Copyright (C) 2009-2013 Typesafe Inc. */ package akka.channels diff --git a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala index 5f23b6fa3f..c102d8fd65 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala @@ -1,5 +1,5 @@ /** - * Copyright (C) 2009-2012 Typesafe Inc. + * Copyright (C) 2009-2013 Typesafe Inc. */ package akka.channels diff --git a/akka-channels/src/main/scala/akka/channels/Channels.scala b/akka-channels/src/main/scala/akka/channels/Channels.scala index 3eb0943ab5..766d3b308f 100644 --- a/akka-channels/src/main/scala/akka/channels/Channels.scala +++ b/akka-channels/src/main/scala/akka/channels/Channels.scala @@ -1,5 +1,5 @@ /** - * Copyright (C) 2009-2012 Typesafe Inc. + * Copyright (C) 2009-2013 Typesafe Inc. */ package akka.channels diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala index 2448f6a958..b213777812 100644 --- a/akka-channels/src/main/scala/akka/channels/Ops.scala +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + package akka.channels import language.experimental.{ macros ⇒ makkros } diff --git a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala index 267aee9115..ca43f8eddb 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Ask.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Ask.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + package akka.channels.macros import akka.channels._ diff --git a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala index d39f5ce201..0ae0fc87c7 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Channel.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Channel.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + package akka.channels.macros import akka.channels._ diff --git a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala index 0f1d4ba7b0..90a6be4251 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + package akka.channels.macros import akka.channels._ diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala index 9e61e5f297..9e67754544 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + package akka.channels.macros import akka.AkkaException diff --git a/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala b/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala index f4b74b49ad..dcaeb3d05d 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Narrow.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + package akka.channels.macros import akka.channels._ diff --git a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala index 13e4019f1b..01bf698a2c 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Tell.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Tell.scala @@ -1,3 +1,7 @@ +/** + * Copyright (C) 2009-2013 Typesafe Inc. + */ + package akka.channels.macros import akka.channels._ diff --git a/akka-channels/src/main/scala/akka/channels/package.scala b/akka-channels/src/main/scala/akka/channels/package.scala index 0b0a53de24..be577b1682 100644 --- a/akka-channels/src/main/scala/akka/channels/package.scala +++ b/akka-channels/src/main/scala/akka/channels/package.scala @@ -1,5 +1,5 @@ /** - * Copyright (C) 2009-2012 Typesafe Inc. + * Copyright (C) 2009-2013 Typesafe Inc. */ package akka From 86ded1fb0b5a07a2c2015dc9dbbcc594a8e6567e Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 31 Jan 2013 18:59:48 +0100 Subject: [PATCH 25/26] review comments - some API docs - require names for top-level actors - allow names for children - flag error when no channels declared --- .../src/main/scala/akka/dispatch/Future.scala | 4 +- .../scala/akka/channels/ChannelSpec.scala | 44 +++++++++---------- .../akka/channels/ChannelExtension.scala | 7 +-- .../main/scala/akka/channels/ChannelRef.scala | 33 +++++++++----- .../main/scala/akka/channels/Channels.scala | 14 ++++-- .../src/main/scala/akka/channels/Ops.scala | 16 +++++++ .../akka/channels/macros/CreateChild.scala | 41 +++++++++++------ .../code/docs/channels/ChannelDocSpec.scala | 8 ++-- akka-docs/rst/scala/typed-channels.rst | 2 +- 9 files changed, 109 insertions(+), 60 deletions(-) diff --git a/akka-actor/src/main/scala/akka/dispatch/Future.scala b/akka-actor/src/main/scala/akka/dispatch/Future.scala index dbd8fbe0f0..dbe6326e69 100644 --- a/akka-actor/src/main/scala/akka/dispatch/Future.scala +++ b/akka-actor/src/main/scala/akka/dispatch/Future.scala @@ -70,8 +70,8 @@ object ExecutionContexts { * It is very useful for actions which are known to be non-blocking and * non-throwing in order to save a round-trip to the thread pool. */ - object sameThreadExecutionContext extends ExecutionContext { - override def execute(runnable: Runnable): Unit = runnable.run() + private[akka] object sameThreadExecutionContext extends ExecutionContext with BatchingExecutor { + override protected def unbatchedExecute(runnable: Runnable): Unit = runnable.run() override def reportFailure(t: Throwable): Unit = throw new IllegalStateException("exception in sameThreadExecutionContext", t) } diff --git a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala index a771af2197..b92e695364 100644 --- a/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala +++ b/akka-channels-tests/src/test/scala/akka/channels/ChannelSpec.scala @@ -126,7 +126,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "Actor with Channels" must { "construct refs" in { - val ref = ChannelExt(system).actorOf(new Tester) + val ref = ChannelExt(system).actorOf(new Tester, "t1") ref <-!- A expectMsg(C) lastSender must be(ref.actorRef) @@ -136,16 +136,16 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "select return channels" in { - val ref = ChannelExt(system).actorOf(new Tester) - implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor)) + val ref = ChannelExt(system).actorOf(new Tester, "t2") + implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor), "t3") ref <-!- A expectMsg(C) lastSender must be(selfChannel.actorRef) } "correctly dispatch to subchannels" in { - val ref = ChannelExt(system).actorOf(new Tester) - implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor)) + val ref = ChannelExt(system).actorOf(new Tester, "t4") + implicit val selfChannel = ChannelExt(system).actorOf(new RecvC(testActor), "t5") ref <-!- A2 expectMsg(C1) lastSender must be(selfChannel.actorRef) @@ -281,7 +281,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "have a working selfChannel" in { - val ref = ChannelExt(system).actorOf(new Children) + val ref = ChannelExt(system).actorOf(new Children, "t10") ref <-!- A expectMsg(C) } @@ -292,7 +292,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, parentChannel <-!- A }) channel[A] { (msg, snd) ⇒ testActor ! msg } - }) + }, "t11") expectMsg(A) } @@ -301,7 +301,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, eval(""" |import akka.channels._ |import ChannelSpec._ - |null.asInstanceOf[ChannelExtension].actorOf(new Actor with Channels[(A, A) :+: TNil, (A, Nothing) :+: TNil] {}) + |null.asInstanceOf[ChannelExtension].actorOf(new Actor with Channels[(A, A) :+: TNil, (A, Nothing) :+: TNil] {}, "") """.stripMargin) }.message must include("type mismatch") } @@ -367,7 +367,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "support narrowing ActorRefs" in { import Channels._ - val channel = ChannelExt(system).actorOf(new RecvC(testActor)) + val channel = ChannelExt(system).actorOf(new RecvC(testActor), "t15") val ref = channel.actorRef implicit val t = Timeout(1.second.dilated) import system.dispatcher @@ -377,7 +377,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "deny wrong narrowing of ActorRefs" in { - val channel = ChannelExt(system).actorOf(new RecvC(testActor)) + val channel = ChannelExt(system).actorOf(new RecvC(testActor), "t16") val ref = channel.actorRef implicit val t = Timeout(1.second.dilated) import system.dispatcher @@ -392,8 +392,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "allow wrapping of ChannelRefs with pass-through" in { - val target = ChannelExt(system).actorOf(new RecvC(testActor)) - val wrap = ChannelExt(system).actorOf(new WriteOnly[C, Nothing](target)) + val target = ChannelExt(system).actorOf(new RecvC(testActor), "t17") + val wrap = ChannelExt(system).actorOf(new WriteOnly[C, Nothing](target), "t18") wrap <-!- C expectMsg(C) lastSender must be(target.actorRef) @@ -404,8 +404,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "allow wrapping of Actor with ChannelsRefs with replies" in { val probe = TestProbe() - val target = ChannelExt(system).actorOf(new EchoTee(probe.ref)) - val wrap = ChannelExt(system).actorOf(new WriteOnly[C, C](target)) + val target = ChannelExt(system).actorOf(new EchoTee(probe.ref), "t19") + val wrap = ChannelExt(system).actorOf(new WriteOnly[C, C](target), "t20") C -!-> wrap expectMsg(C1) expectMsg(C1) @@ -413,14 +413,14 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, } "support typed ask" in { - val t = ChannelExt(system).actorOf(new Tester) + val t = ChannelExt(system).actorOf(new Tester, "t21") implicit val timeout = Timeout(1.second) val r: Future[C] = (t <-?- A).lub Await.result(r, 1.second) must be(C) } "support typed ask with multiple reply channels" in { - val t = ChannelExt(system).actorOf(new SubChannels) + val t = ChannelExt(system).actorOf(new SubChannels, "t22") implicit val timeout = Timeout(1.second) val r: Future[Msg] = (t <-?- A1).lub Await.result(r, 1.second) must be(B) @@ -462,8 +462,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "be able to forward fully generic channels" in { val cd = ChannelExt(system).actorOf(new Actor with Channels[TNil, (C, D) :+: TNil] { channel[C] { (x, snd) ⇒ snd <-!- D } - }) - val t = ChannelExt(system).actorOf(new Poly(cd)) + }, "t25") + val t = ChannelExt(system).actorOf(new Poly(cd), "t26") t <-!- A expectMsg(A) t <-!- C @@ -479,8 +479,8 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, implicit val selfChannel = ChannelExt(system).actorOf(new Actor with Channels[TNil, (C, Nothing) :+:(D, Nothing) :+: TNil] { channel[C] { (c, snd) ⇒ testActor ! c } channel[D] { (d, snd) ⇒ testActor ! d } - }) - val t = ChannelExt(system).actorOf(new Tester) + }, "t27") + val t = ChannelExt(system).actorOf(new Tester, "t28") val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) val fa = Future successful a val b = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](B) @@ -548,7 +548,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "be askable to a ChannelRef" in { implicit val timeout = Timeout(1.second) - val t = ChannelExt(system).actorOf(new Tester) + val t = ChannelExt(system).actorOf(new Tester, "t30") val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) val fa = Future successful a val b = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](B) @@ -608,7 +608,7 @@ class ChannelSpec extends AkkaSpec(ActorSystem("ChannelSpec", AkkaSpec.testConf, "be LUBbable within a Future" in { implicit val timeout = Timeout(1.second) - val t = ChannelExt(system).actorOf(new Tester) + val t = ChannelExt(system).actorOf(new Tester, "t31") val a = new WrappedMessage[(A, Nothing) :+:(B, Nothing) :+: TNil, Msg](A) (Await.result((a -?-> t).lub, timeout.duration): Msg) must be(C) } diff --git a/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala b/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala index 461d0de291..7a1ffffa90 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelExtension.scala @@ -17,9 +17,10 @@ object ChannelExt extends ExtensionKey[ChannelExtension] class ChannelExtension(system: ExtendedActorSystem) extends Extension { - // kick-start the universe (needed due to thread safety issues in runtime mirror) + // FIXME: kick-start the universe (needed due to thread safety issues in runtime mirror) + // see https://issues.scala-lang.org/browse/SI-6240 private val t = typeTag[(Int, Int) :+: TNil] - def actorOf[Ch <: ChannelList](factory: ⇒ Actor with Channels[TNil, Ch]): ChannelRef[Ch] = - new ChannelRef[Ch](system.actorOf(Props(factory))) + def actorOf[Ch <: ChannelList](factory: ⇒ Actor with Channels[TNil, Ch], name: String): ChannelRef[Ch] = + new ChannelRef[Ch](system.actorOf(Props(factory), name)) } diff --git a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala index c102d8fd65..7fe2ee2591 100644 --- a/akka-channels/src/main/scala/akka/channels/ChannelRef.scala +++ b/akka-channels/src/main/scala/akka/channels/ChannelRef.scala @@ -6,27 +6,38 @@ package akka.channels import language.experimental.{ macros ⇒ makkros } import akka.actor.ActorRef -import scala.reflect.runtime.universe.TypeTag -import scala.reflect.macros.Context -import scala.annotation.tailrec -import scala.reflect.macros.Universe -import akka.actor.Actor -import akka.actor.ActorContext import scala.concurrent.Future -import akka.util.Timeout -import akka.AkkaException -import scala.util.control.NoStackTrace - -case class NarrowingException(errors: String) extends AkkaException(errors) with NoStackTrace +/** + * A channel reference, holding a type list of all channels supported by the + * underlying actor. This actor’s reference can be obtained as the `actorRef` + * member. + */ class ChannelRef[+T <: ChannelList](val actorRef: ActorRef) extends AnyVal { + /** + * Send a message over this channel, “tell” semantics, returning the message. + */ def <-!-[M](msg: M): M = macro macros.Tell.impl[T, M] + /** + * Eventually send the value contained in the future over this channel, + * “tell” semantics, returning a Future which is completed after sending + * with the value which was sent (“Future.andThen” semantics). + */ def <-!-[M](future: Future[M]): Future[M] = macro macros.Tell.futureImpl[T, M] + /** + * Send a message over this channel, “ask” semantics, returning a Future + * which will be completed with the reply message or a TimeoutException. + * If the message is a Future itself, eventually send the Future’s value. + */ def <-?-[M](msg: M): Future[_] = macro macros.Ask.impl[ChannelList, Any, T, M] + /** + * Narrow this ChannelRef by removing channels or narrowing input types or + * widening output types. + */ def narrow[C <: ChannelList]: ChannelRef[C] = macro macros.Narrow.impl[C, T] } diff --git a/akka-channels/src/main/scala/akka/channels/Channels.scala b/akka-channels/src/main/scala/akka/channels/Channels.scala index 766d3b308f..beae4b9cb5 100644 --- a/akka-channels/src/main/scala/akka/channels/Channels.scala +++ b/akka-channels/src/main/scala/akka/channels/Channels.scala @@ -51,6 +51,13 @@ trait Channels[P <: ChannelList, C <: ChannelList] { this: Actor ⇒ */ def createChild[Pa <: ChannelList, Ch <: ChannelList](factory: Actor with Channels[Pa, Ch]): ChannelRef[Ch] = macro macros.CreateChild.impl[C, Pa, Ch] + /** + * Create a child actor with properly typed ChannelRef and the given name, + * verifying that this actor can handle everything which the child tries to + * send via its `parent` ChannelRef. + */ + def createChild[Pa <: ChannelList, Ch <: ChannelList](factory: Actor with Channels[Pa, Ch], name: String): ChannelRef[Ch] = macro macros.CreateChild.implName[C, Pa, Ch] + /** * Properly typed ChannelRef for the context.parent. */ @@ -131,6 +138,8 @@ trait Channels[P <: ChannelList, C <: ChannelList] { this: Actor ⇒ private def verifyCompleteness() { val channels = inputChannels(ru)(channelListTypeTag.tpe) + if (channels.isEmpty) + throw ActorInitializationException("Actor with Channels cannot have no channels") val classes = channels groupBy (e ⇒ channelListTypeTag.mirror.runtimeClass(e.widen)) val missing = classes.keySet -- behavior.keySet if (missing.nonEmpty) { @@ -162,10 +171,7 @@ trait Channels[P <: ChannelList, C <: ChannelList] { this: Actor ⇒ case c: CheckType[_] ⇒ true case _ ⇒ val msgClass = x.getClass - index find (_ isAssignableFrom msgClass) match { - case None ⇒ false - case Some(cls) ⇒ true - } + index exists (_ isAssignableFrom msgClass) } } } diff --git a/akka-channels/src/main/scala/akka/channels/Ops.scala b/akka-channels/src/main/scala/akka/channels/Ops.scala index b213777812..59c4549c8e 100644 --- a/akka-channels/src/main/scala/akka/channels/Ops.scala +++ b/akka-channels/src/main/scala/akka/channels/Ops.scala @@ -12,6 +12,7 @@ import scala.concurrent.{ ExecutionContext, Future } import scala.reflect.runtime.{ universe ⇒ ru } import scala.util.Success import akka.dispatch.ExecutionContexts +import scala.util.control.NoStackTrace sealed trait ChannelList sealed trait TNil extends ChannelList @@ -26,8 +27,23 @@ sealed trait ReplyChannels[T <: ChannelList] extends ChannelList */ sealed trait UnknownDoNotWriteMeDown +/** + * This exception is used to signal errors when trying to `.narrow` an + * ActorRef into a ChannelRef: if the actor finds the requested channel types + * incompatible with its selfChannel, it will return errors in the same format + * as would occur during compilation of a `ChannelRef.narrow` operation. + */ +case class NarrowingException(message: String) extends akka.AkkaException(message) with NoStackTrace + class ActorRefOps(val ref: ActorRef) extends AnyVal { import macros.Helpers._ + + /** + * Send a query to the actor and check whether it supports the requested + * channel types; the normal timeout semantics of the `ask` pattern apply. + * The Future will be completed either with the desired ChannelRef or with + * an exception (TimeoutException or NarrowingException). + */ def narrow[C <: ChannelList](implicit timeout: Timeout, ec: ExecutionContext, tt: ru.TypeTag[C]): Future[ChannelRef[C]] = { import Channels._ ref ? CheckType(tt) map { diff --git a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala index 90a6be4251..de7d7d278b 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/CreateChild.scala @@ -22,20 +22,35 @@ object CreateChild { import c.universe._ - if (weakTypeOf[ParentChannels] =:= weakTypeOf[Nothing]) { - c.abort(c.enclosingPosition, "Parent argument must not be Nothing") - } - if (weakTypeOf[ChildChannels] =:= weakTypeOf[Nothing]) { - c.abort(c.enclosingPosition, "channel list must not be Nothing") - } + verify(c)(weakTypeOf[ParentChannels], weakTypeOf[ChildChannels], weakTypeOf[MyChannels]) - val missing = missingChannels(c.universe)(weakTypeOf[MyChannels], inputChannels(c.universe)(weakTypeOf[ParentChannels])) - if (missing.isEmpty) { - implicit val t = c.TypeTag[ChildChannels](c.weakTypeOf[ChildChannels]) - reify(new ChannelRef[ChildChannels](c.prefix.splice.context.actorOf(Props(factory.splice)))) - } else { - c.abort(c.enclosingPosition, s"This actor cannot support a child requiring channels ${missing mkString ", "}") - } + implicit val t = c.TypeTag[ChildChannels](c.weakTypeOf[ChildChannels]) + reify(new ChannelRef[ChildChannels](c.prefix.splice.context.actorOf(Props(factory.splice)))) + } + + def implName[MyChannels <: ChannelList: c.WeakTypeTag, ParentChannels <: ChannelList: c.WeakTypeTag, ChildChannels <: ChannelList: c.WeakTypeTag]( + c: Context { + type PrefixType = Actor with Channels[_, MyChannels] + })(factory: c.Expr[Actor with Channels[ParentChannels, ChildChannels]], name: c.Expr[String]): c.Expr[ChannelRef[ChildChannels]] = { + + import c.universe._ + + verify(c)(weakTypeOf[ParentChannels], weakTypeOf[ChildChannels], weakTypeOf[MyChannels]) + + implicit val t = c.TypeTag[ChildChannels](c.weakTypeOf[ChildChannels]) + reify(new ChannelRef[ChildChannels](c.prefix.splice.context.actorOf(Props(factory.splice), name.splice))) + } + + def verify(c: Context)(parent: c.Type, child: c.Type, mine: c.Type): Unit = { + import c.universe._ + + val nothing = weakTypeOf[Nothing] + if (parent =:= nothing) abort(c, "Parent argument must not be Nothing") + if (child =:= nothing) abort(c, "channel list must not be Nothing") + + val missing = missingChannels(c.universe)(mine, inputChannels(c.universe)(parent)) + if (missing.nonEmpty) + abort(c, s"This actor cannot support a child requiring channels ${missing mkString ", "}") } } \ No newline at end of file diff --git a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala index ef74019446..31645655ff 100644 --- a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala +++ b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala @@ -40,7 +40,7 @@ object ChannelDocSpec { class Parent extends Actor with Channels[TNil, (Stats, Nothing) :+: (GetChild.type, ChildRef) :+: TNil] { - + val child = createChild(new Child) channel[GetChild.type] { (_, snd) ⇒ ChildRef(child) -!-> snd } @@ -176,7 +176,7 @@ class ChannelDocSpec extends AkkaSpec { // type given just for demonstration purposes val latch: ChannelRef[(Request, Reply) :+: (String, Int) :+: TNil] = - ChannelExt(system).actorOf(new Latch(target)) + ChannelExt(system).actorOf(new Latch(target), "latch") "hello" -!-> latch //#processing @@ -202,8 +202,8 @@ class ChannelDocSpec extends AkkaSpec { // // then it is used somewhat like this: // - - val parent = ChannelExt(system).actorOf(new Parent) + + val parent = ChannelExt(system).actorOf(new Parent, "parent") parent <-!- GetChild val child = expectMsgType[ChildRef].child // this assumes TestKit context diff --git a/akka-docs/rst/scala/typed-channels.rst b/akka-docs/rst/scala/typed-channels.rst index a0b2574443..94348bf9d3 100644 --- a/akka-docs/rst/scala/typed-channels.rst +++ b/akka-docs/rst/scala/typed-channels.rst @@ -40,7 +40,7 @@ an error, since the companion object ``Command`` is not an instance of type While this example looks pretty simple, the implications are profound. In order to be useful, the system must be as reliable as you would expect a type system -to be. This means that unless you step outside of the it (i.e. doing the +to be. This means that unless you step outside of it (i.e. doing the equivalent of ``.asInstanceOf[_]``) you shall be protected, failures shall be recognized and flagged. There are a number of challenges included in this requirement, which are discussed in the following sections. If you are reading From b127ab0d4f89a1d089a2779603fdbcf618bf2c17 Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 1 Feb 2013 15:32:56 +0100 Subject: [PATCH 26/26] final review comments - make it EXPERIMENTAL - shuffle docs around to be less scary - reuse sameThreadExecutionContext in CircuitBreaker - typos --- .../scala/akka/pattern/CircuitBreaker.scala | 13 +- .../scala/akka/channels/macros/Helpers.scala | 9 +- .../code/docs/channels/ChannelDocSpec.scala | 24 +- akka-docs/rst/scala/typed-channels.rst | 414 ++++++++++-------- project/AkkaBuild.scala | 2 +- 5 files changed, 240 insertions(+), 222 deletions(-) diff --git a/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala b/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala index aa578baf38..628b847700 100644 --- a/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala +++ b/akka-actor/src/main/scala/akka/pattern/CircuitBreaker.scala @@ -13,20 +13,13 @@ import scala.concurrent.{ ExecutionContext, Future, Promise, Await } import scala.concurrent.duration._ import scala.util.control.NonFatal import scala.util.Success +import akka.dispatch.ExecutionContexts.sameThreadExecutionContext /** * Companion object providing factory methods for Circuit Breaker which runs callbacks in caller's thread */ object CircuitBreaker { - /** - * Synchronous execution context to run in caller's thread - used by companion object factory methods - */ - private[CircuitBreaker] val syncExecutionContext = new ExecutionContext { - override def execute(runnable: Runnable): Unit = runnable.run() - override def reportFailure(t: Throwable): Unit = () - } - /** * Callbacks run in caller's thread when using withSyncCircuitBreaker, and in same ExecutionContext as the passed * in Future when using withCircuitBreaker. To use another ExecutionContext for the callbacks you can specify the @@ -38,7 +31,7 @@ object CircuitBreaker { * @param resetTimeout [[scala.concurrent.duration.FiniteDuration]] of time after which to attempt to close the circuit */ def apply(scheduler: Scheduler, maxFailures: Int, callTimeout: FiniteDuration, resetTimeout: FiniteDuration): CircuitBreaker = - new CircuitBreaker(scheduler, maxFailures, callTimeout, resetTimeout)(syncExecutionContext) + new CircuitBreaker(scheduler, maxFailures, callTimeout, resetTimeout)(sameThreadExecutionContext) /** * Callbacks run in caller's thread when using withSyncCircuitBreaker, and in same ExecutionContext as the passed @@ -301,7 +294,7 @@ class CircuitBreaker(scheduler: Scheduler, maxFailures: Int, callTimeout: Finite bodyFuture.onComplete({ case s: Success[_] if !deadline.isOverdue() ⇒ callSucceeds() case _ ⇒ callFails() - })(CircuitBreaker.syncExecutionContext) + })(sameThreadExecutionContext) bodyFuture } diff --git a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala index 9e67754544..5a18741902 100644 --- a/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala +++ b/akka-channels/src/main/scala/akka/channels/macros/Helpers.scala @@ -151,11 +151,10 @@ object Helpers { final def unwrapMsgType(u: Universe)(msg: u.Type): u.Type = { import u._ - if (msg <:< typeOf[WrappedMessage[_, _]]) - msg match { - case TypeRef(_, _, x :: _) ⇒ x - } - else msg + msg match { + case TypeRef(_, _, x :: _) if msg <:< typeOf[WrappedMessage[_, _]] ⇒ x + case x ⇒ x + } } } \ No newline at end of file diff --git a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala index 31645655ff..f45247c8ad 100644 --- a/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala +++ b/akka-docs/rst/scala/code/docs/channels/ChannelDocSpec.scala @@ -57,10 +57,10 @@ class ChannelDocSpec extends AkkaSpec { import ChannelDocSpec._ - class A - class B - class C - class D + class MsgA + class MsgB + class MsgC + class MsgD "demonstrate why Typed Channels" in { def someActor = testActor @@ -79,7 +79,7 @@ class ChannelDocSpec extends AkkaSpec { type Example = // //#motivation-types - (A, B) :+: (C, D) :+: TNil + (MsgA, MsgB) :+: (MsgC, MsgD) :+: TNil //#motivation-types } @@ -114,12 +114,12 @@ class ChannelDocSpec extends AkkaSpec { implicit val dummySender: ChannelRef[(Any, Nothing) :+: TNil] = ??? implicit val timeout: Timeout = ??? // for the ask operations - val channelA: ChannelRef[(A, B) :+: TNil] = ??? - val channelB: ChannelRef[(B, C) :+: TNil] = ??? - val channelC: ChannelRef[(C, D) :+: TNil] = ??? + val channelA: ChannelRef[(MsgA, MsgB) :+: TNil] = ??? + val channelB: ChannelRef[(MsgB, MsgC) :+: TNil] = ??? + val channelC: ChannelRef[(MsgC, MsgD) :+: TNil] = ??? - val a = new A - val fA = Future { new A } + val a = new MsgA + val fA = Future { new MsgA } channelA <-!- a // send a to channelA a -!-> channelA // same thing as above @@ -128,8 +128,8 @@ class ChannelDocSpec extends AkkaSpec { fA -!-> channelA // same thing as above // ask the actor; return type given in full for illustration - val fB: Future[WrappedMessage[(B, Nothing) :+: TNil, B]] = channelA <-?- a - val fBunwrapped: Future[B] = fB.lub + val fB: Future[WrappedMessage[(MsgB, Nothing) :+: TNil, MsgB]] = channelA <-?- a + val fBunwrapped: Future[MsgB] = fB.lub a -?-> channelA // same thing as above diff --git a/akka-docs/rst/scala/typed-channels.rst b/akka-docs/rst/scala/typed-channels.rst index 94348bf9d3..0ea47bdfc0 100644 --- a/akka-docs/rst/scala/typed-channels.rst +++ b/akka-docs/rst/scala/typed-channels.rst @@ -1,8 +1,14 @@ .. _typed-channels: -############## -Typed Channels -############## +############################# +Typed Channels (EXPERIMENTAL) +############################# + +.. note:: + + *This is a preview of the upcoming Typed Channels support, its API may change + during development up to the released version where the EXPERIMENTAL label is + removed.* Motivation ========== @@ -43,197 +49,7 @@ to be useful, the system must be as reliable as you would expect a type system to be. This means that unless you step outside of it (i.e. doing the equivalent of ``.asInstanceOf[_]``) you shall be protected, failures shall be recognized and flagged. There are a number of challenges included in this -requirement, which are discussed in the following sections. If you are reading -this chapter for the first time and are not currently interested in exactly why -things are as they are, you may skip ahead to `Terminology`_. - -The Type Pollution Problem --------------------------- - -What if an actor accepts two different types of messages? It might be a main -communications channel which is forwarded to worker actors for performing some -long-running and/or dangerous task, plus an administrative channel for the -routing of requests. Or it might be a generic message throttler which accepts a -generic channel for passing it through (which delay where appropriate) and a -management channel for setting the throttling rate. In the second case it is -especially easy to see that those two channels will probably not be related, -their types will not be derived from a meaningful common supertype; instead the -least upper bound will probably be :class:`AnyRef`. If a typed channel -reference only had the capability to express a single type, this type would -then be no restriction anymore. - -One solution to this is to never expose references describe more than one -channel at a time. But where would these references come from? It would be very -difficult to make this construction process type-safe, and it would also be an -inconvenient restriction, since message ordering guarantees only apply for the -same sender–receive pair, and if there are relations between the messages sent -on multiple channels those would need more boilerplate code to realize than if -all interaction were possible through a single reference. - -The other solution thus is to express multiple channel types by a single -channel reference, which requires the implementation of type lists and -computations on these. And as we will see below it also requires the -specification of possibly multiple reply channels per input type, hence a type -map. The implementation chosen uses type lists like this: - -.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation-types - -This type expresses two channels: type ``A`` may stimulate replies of type -``B``, while type ``C`` may evoke replies of type ``D``. The type operator -``:+:`` is a binary type which form a list of these channel definitions, and -like every good list it ends with an empty terminator ``TNil``. - -The Reply Problem ------------------ - -Akka actors have the power to reply to any message they receive, which is also -a message send and shall also be covered by typed channels. Since the sending -actor is the one which will also receive the reply, this needs to be verified. -The solution to this problem is that in addition to the ``self`` reference, -which is implicitly picked up as the sender for untyped actor interactions, -there is also a ``selfChannel`` which describes the typed channels handled by -this actor. Thus at the call site of the message send it must be verified that -this actor can actually handle the reply for that given message send. - -The Sender Ping-Pong Problem ----------------------------- - -After successfully sending a message to an actor over a typed channel, that -actor will have a reference to the message’s sender, because normal Akka -message processing rules apply. For this sender reference there must exist a -typed channel reference which describes the possible reply types which are -applicable for each of the incoming message channels. We will see below how -this reference is provided in the code, the problem we want to highlight here -is a different one: the nature of any sender reference is that it is highly -dynamic, the compiler cannot possibly know who sent the message we are -currently processing. - -But this does not mean that all hope is lost: the solution is to do *all* -type-checking at the call site of the message send. The receiving actor just -needs to declare its channel descriptions in its own type, and channel -references are derived at construction from this type (implying the existence -of a typed ``actorOf``). Then the actor knows for each received message type -which the allowed reply types are. The typed channel for the sender reference -hence has the reply types for the current input channel as its own input types, -but what should the reply types be? This is the ping-pong problem: - -* ActorA sends MsgA to ActorB - -* ActorB replies with MsgB - -* ActorA replies with MsgC - -Every “reply” uses the sender channel, which is dynamic and hence only known -partially. But ActorB did not know who sent the message it just replied to and -hence it cannot check that it can process the possible replies following that -message send. Only ActorA could have known, because it knows its own channels -as well as ActorB’s channels completely. The solution is thus to recursively -verify the message send, following all reply channels until all possible -message types to be sent have been verified. This sounds horribly complex, but -the algorithm for doing so actually has a worst-case complexity of O(N) where N -is the number of input channels of ActorA or ActorB, whoever has fewer. - -The Parent Problem ------------------- - -There is one other actor reference which is available to ever actor: its -parent. Since the child–parent relationship is established permanently when the -child is created by the parent, this problem is easily solvable by encoding the -requirements of the child for its parent channel in its type signature having -the typed variant of ``actorOf`` verify this against the ``selfChannel``. - -Anecdotally, since the guardian actor does not care at all about message sent -to it, top-level type channel actors must declare their parent channel to be -empty. - -The Exposure/Restriction Problem --------------------------------- - -An actor may provide more than one service, either itself or by proxy, each -with their own set of channels. Only having references for the full set of -channels leads to a too wide spread of capabilities: in the example of the -message rate throttling actor its management channel is only meant to be used -by the actor which inserted it, not by the two actors between it was inserted. -Hence the manager will have to create a channel reference which excludes the -management channels before handing out the reference to other actors. - -Another variant of this problem is an actor which handles a channel whose input -type is a supertype for a number of derived channels. It should be allowed to -use the “superchannel” in place of any of the subchannels, but not the other -way around. The intuitive approach would be to model this by making the channel -reference contravariant in its channel types and define those channel types -accordingly. This does not work nicely, however, because Scala’s type system is -not well-suited to modeling such calculations on unordered type lists; it might -be possible but its implementation would be forbiddingly complex. - -Therefore this topic gained traction as macros became available: being able to -write down type calculations using standard collections and their -transformations reduces the implementation to a handful of lines. The “narrow” -operation implemented this way allows all narrowing of input channels and -widening of output channels down to ``(Nothing, Any)`` (which is to say: -removal). - -The Forwarding Problem ----------------------- - -One important feature of actors mentioned above is their composability which is -enabled by being able to forward or delegate messages. It is the nature of this -process that the sending party is not aware of the true destination of the -message, it only sees the façade in front of it. Above we have seen that the -sender ping-pong problem requires all verification to be performed at the -sender’s end, but if the sender does not know the final recipient, how can it -check that the message exchange is type-safe? - -The forwarding party—the middle-man—is also not in the position to make this -call, since all it has is the incomplete sender channel which is lacking reply -type information. The problem which arises lies precisely in these reply -sequences: the ping-pong scheme was verified against the middle-man, and if the -final recipient would reply to the forwarded request, that sender reference -would belong to a different channel and there is no single location in the -source code where all these pieces are known at compile time. - -The solution to this problem is not to allow forwarding in the normal untyped -:class:`ActorRef` sense. Replies must always be sent by the recipient of the -original message in order for the type checks at the sender site to be -effective. Since forwarding is an important communication pattern among actors, -support for it is thus provided in the form of the :meth:`ask` pattern combined -with the :meth:`pipe` pattern, which both are not add-ons but fully integrated -operations among typed channels. - -The JVM Erasure Problem ------------------------ - -When an actor with typed channels receives a message, this message needs to be -dispatched internally to the right channel, so that the right sender channel -can be presented and so on. This dispatch needs to work with the information -contained in the message, which due to the erasure of generic type information -is an incomplete image of the true channel types. Those full types exist only -at compile-time and reifying them into TypeTags at runtime for every message -send would be prohibitively expensive. This means that channels which erase to -the same JVM type cannot coexist within the same actor, message would not be -routable reliably in that case. - -The Actor Lookup Problem ------------------------- - -Everything up to this point has assumed that channel references are passed from -their point of creation to their point of use directly and in the regime of -strong, unerased types. This can also happen between actors by embedding them -in case classes with proper type information. But one particular useful feature -of Akka actors is that they have a stable identity by which they can be found, -a unique name. This name is represented as a :class:`String` and naturally does -not bear any type information concerning the actor’s channels. Thus, when -looking up an actor with ``system.actorFor(...)`` you will only get an untyped -:class:`ActorRef` and not a channel reference. This :class:`ActorRef` can of -course manually be wrapped in a channel reference bearing the desired channels, -but this is not a type-safe operation. - -The solution in this case must be a runtime check. There is an operation to -“narrow” an :class:`ActorRef` to a channel reference of given type, which -behind the scenes will send a message to the designated actor with a TypeTag -representing the requested channels. The actor will check these against its own -TypeTag and reply with the verification result. This check uses the same code -as the compile-time “narrow” operation introduced above. +requirement, which are discussed in `The Design Background`_ below. Terminology =========== @@ -477,7 +293,217 @@ possible if that subchannel was declared up-front. TypeTags are currently (Scala 2.10.0) not serializable, hence narrowing of :class:`ActorRef` does not work for remote references. +The Design Background +===================== + +This section outlines the most prominent challenges encountered during the +development of Typed Channels and the rationale for their solutions. It is not +necessary to understand this material in order to use Typed Channels, but it +may be useful to explain why certain things are as they are. + +The Type Pollution Problem +-------------------------- + +What if an actor accepts two different types of messages? It might be a main +communications channel which is forwarded to worker actors for performing some +long-running and/or dangerous task, plus an administrative channel for the +routing of requests. Or it might be a generic message throttler which accepts a +generic channel for passing it through (which delay where appropriate) and a +management channel for setting the throttling rate. In the second case it is +especially easy to see that those two channels will probably not be related, +their types will not be derived from a meaningful common supertype; instead the +least upper bound will probably be :class:`AnyRef`. If a typed channel +reference only had the capability to express a single type, this type would +then be no restriction anymore. + +One solution to this is to never expose references describe more than one +channel at a time. But where would these references come from? It would be very +difficult to make this construction process type-safe, and it would also be an +inconvenient restriction, since message ordering guarantees only apply for the +same sender–receive pair, and if there are relations between the messages sent +on multiple channels those would need more boilerplate code to realize than if +all interaction were possible through a single reference. + +The other solution thus is to express multiple channel types by a single +channel reference, which requires the implementation of type lists and +computations on these. And as we will see below it also requires the +specification of possibly multiple reply channels per input type, hence a type +map. The implementation chosen uses type lists like this: + +.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation-types + +This type expresses two channels: type ``A`` may stimulate replies of type +``B``, while type ``C`` may evoke replies of type ``D``. The type operator +``:+:`` is a binary type which form a list of these channel definitions, and +like every good list it ends with an empty terminator ``TNil``. + +The Reply Problem +----------------- + +Akka actors have the power to reply to any message they receive, which is also +a message send and shall also be covered by typed channels. Since the sending +actor is the one which will also receive the reply, this needs to be verified. +The solution to this problem is that in addition to the ``self`` reference, +which is implicitly picked up as the sender for untyped actor interactions, +there is also a ``selfChannel`` which describes the typed channels handled by +this actor. Thus at the call site of the message send it must be verified that +this actor can actually handle the reply for that given message send. + +The Sender Ping-Pong Problem +---------------------------- + +After successfully sending a message to an actor over a typed channel, that +actor will have a reference to the message’s sender, because normal Akka +message processing rules apply. For this sender reference there must exist a +typed channel reference which describes the possible reply types which are +applicable for each of the incoming message channels. We will see below how +this reference is provided in the code, the problem we want to highlight here +is a different one: the nature of any sender reference is that it is highly +dynamic, the compiler cannot possibly know who sent the message we are +currently processing. + +But this does not mean that all hope is lost: the solution is to do *all* +type-checking at the call site of the message send. The receiving actor just +needs to declare its channel descriptions in its own type, and channel +references are derived at construction from this type (implying the existence +of a typed ``actorOf``). Then the actor knows for each received message type +which the allowed reply types are. The typed channel for the sender reference +hence has the reply types for the current input channel as its own input types, +but what should the reply types be? This is the ping-pong problem: + +* ActorA sends MsgA to ActorB + +* ActorB replies with MsgB + +* ActorA replies with MsgC + +Every “reply” uses the sender channel, which is dynamic and hence only known +partially. But ActorB did not know who sent the message it just replied to and +hence it cannot check that it can process the possible replies following that +message send. Only ActorA could have known, because it knows its own channels +as well as ActorB’s channels completely. The solution is thus to recursively +verify the message send, following all reply channels until all possible +message types to be sent have been verified. This sounds horribly complex, but +the algorithm for doing so actually has a worst-case complexity of O(N) where N +is the number of input channels of ActorA or ActorB, whoever has fewer. + +The Parent Problem +------------------ + +There is one other actor reference which is available to every actor: its +parent. Since the child–parent relationship is established permanently when the +child is created by the parent, this problem is easily solvable by encoding the +requirements of the child for its parent channel in its type signature having +the typed variant of ``actorOf`` verify this against the ``selfChannel``. + +Anecdotally, since the guardian actor does not care at all about message sent +to it, top-level type channel actors must declare their parent channel to be +empty. + +The Exposure/Restriction Problem +-------------------------------- + +An actor may provide more than one service, either itself or by proxy, each +with their own set of channels. Only having references for the full set of +channels leads to a too wide spread of capabilities: in the example of the +message rate throttling actor its management channel is only meant to be used +by the actor which inserted it, not by the two actors between it was inserted. +Hence the manager will have to create a channel reference which excludes the +management channels before handing out the reference to other actors. + +Another variant of this problem is an actor which handles a channel whose input +type is a supertype for a number of derived channels. It should be allowed to +use the “superchannel” in place of any of the subchannels, but not the other +way around. The intuitive approach would be to model this by making the channel +reference contravariant in its channel types and define those channel types +accordingly. This does not work nicely, however, because Scala’s type system is +not well-suited to modeling such calculations on unordered type lists; it might +be possible but its implementation would be forbiddingly complex. + +Therefore this topic gained traction as macros became available: being able to +write down type calculations using standard collections and their +transformations reduces the implementation to a handful of lines. The “narrow” +operation implemented this way allows all narrowing of input channels and +widening of output channels down to ``(Nothing, Any)`` (which is to say: +removal). + +The Forwarding Problem +---------------------- + +One important feature of actors mentioned above is their composability which is +enabled by being able to forward or delegate messages. It is the nature of this +process that the sending party is not aware of the true destination of the +message, it only sees the façade in front of it. Above we have seen that the +sender ping-pong problem requires all verification to be performed at the +sender’s end, but if the sender does not know the final recipient, how can it +check that the message exchange is type-safe? + +The forwarding party—the middle-man—is also not in the position to make this +call, since all it has is the incomplete sender channel which is lacking reply +type information. The problem which arises lies precisely in these reply +sequences: the ping-pong scheme was verified against the middle-man, and if the +final recipient would reply to the forwarded request, that sender reference +would belong to a different channel and there is no single location in the +source code where all these pieces are known at compile time. + +The solution to this problem is not to allow forwarding in the normal untyped +:class:`ActorRef` sense. Replies must always be sent by the recipient of the +original message in order for the type checks at the sender site to be +effective. Since forwarding is an important communication pattern among actors, +support for it is thus provided in the form of the :meth:`ask` pattern combined +with the :meth:`pipe` pattern, which both are not add-ons but fully integrated +operations among typed channels. + +The JVM Erasure Problem +----------------------- + +When an actor with typed channels receives a message, this message needs to be +dispatched internally to the right channel, so that the right sender channel +can be presented and so on. This dispatch needs to work with the information +contained in the message, which due to the erasure of generic type information +is an incomplete image of the true channel types. Those full types exist only +at compile-time and reifying them into TypeTags at runtime for every message +send would be prohibitively expensive. This means that channels which erase to +the same JVM type cannot coexist within the same actor, message would not be +routable reliably in that case. + +The Actor Lookup Problem +------------------------ + +Everything up to this point has assumed that channel references are passed from +their point of creation to their point of use directly and in the regime of +strong, unerased types. This can also happen between actors by embedding them +in case classes with proper type information. But one particular useful feature +of Akka actors is that they have a stable identity by which they can be found, +a unique name. This name is represented as a :class:`String` and naturally does +not bear any type information concerning the actor’s channels. Thus, when +looking up an actor with ``system.actorFor(...)`` you will only get an untyped +:class:`ActorRef` and not a channel reference. This :class:`ActorRef` can of +course manually be wrapped in a channel reference bearing the desired channels, +but this is not a type-safe operation. + +The solution in this case must be a runtime check. There is an operation to +“narrow” an :class:`ActorRef` to a channel reference of given type, which +behind the scenes will send a message to the designated actor with a TypeTag +representing the requested channels. The actor will check these against its own +TypeTag and reply with the verification result. This check uses the same code +as the compile-time “narrow” operation introduced above. + How to read The Types ===================== +In case of errors in your code the compiler will try to inform you in the most +precise way it can, and that will then contain types like this:: + akka.channels.:+:[(com.example.Request, com.example.Reply), + akka.channels.:+:[(com.example.Command, Nothing), TNil]] + +These types look unwieldy because of two things: they use fully qualified names +for all the types (thankfully using the ``()`` sugar for :class:`Tuple2`), and +they do not employ infix notation. That same type there might look like this in +your source code:: + + (Request, Reply) :+: (Command, Nothing) :+: TNil + +As soon as someone finds the time, it would be nice if the IDEs learned to +print types making use of the file’s import statements and infix notation. diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 123d5ed125..90448f45d4 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -437,7 +437,7 @@ object AkkaBuild extends Build { ) configs (MultiJvm) lazy val channels = Project( - id = "akka-channels", + id = "akka-channels-experimental", base = file("akka-channels"), dependencies = Seq(actor), settings = defaultSettings ++ Seq(