major facelift: -!-> and -?-> appear
- 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
This commit is contained in:
parent
cfcc9da9bc
commit
e862890ded
18 changed files with 718 additions and 572 deletions
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
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 {
|
||||
|
||||
// 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] =
|
||||
new ChannelRef[Ch](system.actorOf(Props(factory)))
|
||||
}
|
||||
30
akka-channels/src/main/scala/akka/channels/ChannelRef.scala
Normal file
30
akka-channels/src/main/scala/akka/channels/ChannelRef.scala
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
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]
|
||||
|
||||
}
|
||||
172
akka-channels/src/main/scala/akka/channels/Channels.scala
Normal file
172
akka-channels/src/main/scala/akka/channels/Channels.scala
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
36
akka-channels/src/main/scala/akka/channels/Ops.scala
Normal file
36
akka-channels/src/main/scala/akka/channels/Ops.scala
Normal file
|
|
@ -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
|
||||
63
akka-channels/src/main/scala/akka/channels/macros/Ask.scala
Normal file
63
akka-channels/src/main/scala/akka/channels/macros/Ask.scala
Normal file
|
|
@ -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[_]])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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))))))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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(???)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
119
akka-channels/src/main/scala/akka/channels/macros/Helpers.scala
Normal file
119
akka-channels/src/main/scala/akka/channels/macros/Helpers.scala
Normal file
|
|
@ -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(<T1>, <T2>, ...) into a ChannelList
|
||||
* ( Channel[<T1>, Nothing] :=: Channel[<T2>, 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])
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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]])
|
||||
}
|
||||
}
|
||||
90
akka-channels/src/main/scala/akka/channels/macros/Tell.scala
Normal file
90
akka-channels/src/main/scala/akka/channels/macros/Tell.scala
Normal file
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
15
akka-channels/src/main/scala/akka/channels/package.scala
Normal file
15
akka-channels/src/main/scala/akka/channels/package.scala
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue