在 Akka 中,除了使用 RPC 风格编程模型的“Typed Actor”API 之外,是否有一种方法可以将发送给 Actor 的消息限制为特定的静态类型?
我可以在 Akka 中使用消息传递风格而不放弃 Actor 边界的静态类型安全吗?
例如,我想使用这样的代码:
sealed abstract class FooMessage
case object Foo extends FooMessage
case object Bar extends FooMessage
class FooActor extends Actor[FooMessage] {
def receive = {
case Foo => () // OK
// Would raise a compiler error:
// case s: String => error("Can't happen, String is not a subtype of FooMessage")
}
}
val fooActor = actorOf[FooActor]
fooActor ! Foo // OK
// Won't compile:
fooActor ! "Hello"
也许必须扩展一些基本特征或具有像
Either
这样的构造才能允许系统级消息(Exit
等)。
然后您必须将消息类型编码到 Actor 引用中,这将大大减少 ActorRegistry 等内容的值。
此外,使用“成为”(这是演员模型的基础)等强大的机制,输入消息的价值就不那么高了。
由于当消息与当前行为不匹配时,Akka 不会泄漏内存,因此不存在将“错误”消息发送给“错误”参与者的相同风险。
此外,Actor 本质上是动态的,所以如果你想让它们静态,请使用 TypedActor (这不是 RPC,它就像常规 Actor 一样 RPC,void 方法是 ! 调用,Future 返回类型是 !!! 和其他返回类型基于!!)
常见的做法是在 Actor 的伴生对象中声明 Actor 可以接收哪些消息,这使得更容易知道它可以接收什么。
这有帮助吗?
在 Scala stdlib 中,有一个借口可以让基本的 actor 成为非类型化(这不适用于 Akka,因为我记得它不支持嵌套接收)。 Lift 则支持开箱即用的类型化 Actor。
但是,使用通道,仍然可以使用 stdlib 创建强类型参与者:
object TypedActor {
def apply[A](fun: PartialFunction[A, Any]): OutputChannel[A] = {
val sink = new SyncVar[Channel[A]]
actor {
val in = new Channel[A](self)
sink set in
loop {
in react { case any => reply(fun(any)) }
}
}
sink.get
}
}
sealed abstract class FooMessage
case object Foo extends FooMessage
case object Bar extends FooMessage
object Test {
val fooActor = TypedActor[FooMessage]{
case Foo => println("OK")
}
fooActor ! Foo
fooActor ! "Hello!" // doesn't compile -> Type mismatch; found: String("Hello!"); required: FooMessage;
}
实际上限制 Actor 仅具有单一类型作为输入并不是很有用。对我来说更有用的是以严格类型化的方式列出可能的输入。
有一种对参与者进行严格类型化输入的方法(SynapseGrid):
case class Contact[T](...)
case class Signal[T](contact:Contact[T], data:T)
在您的情况下,界面由单个输入触点组成:
val FooInput = contact[FooMessage]("FooInput")
在 SynapseGrid 框架内,信号处理是使用 Builder 定义的:
class FooActorBuilder extends SystemBuilder {
inputs(FooInput, OtherInput)
FooInput.foreach(fooMessage => () //OK
)
OtherInput.foreach(...)
}
显然无法构造具有不兼容类型的 Signal。因此我们进行了编译时检查。在 SynapseGrid 中,有一个用于处理信号和联系人的 DSL。例如,从外部发送 Foo 或 Bar:
val SomeOtherContact = contact[Boolean]("SomeOtherContact")
SomeOtherContact.map(flag => if(flag) Foo else Bar) >> FooInput
当然也可以直接发消息:
val inputMessage = Signal(FooInput, Foo)
actor ! inputMessage
听起来 Akka 的 Akka 的类型化通道支持已经解决了这个问题,但是(根据 评论 已经在 2.3 版本中从 Akka 中删除了)。
在 Akka 2.2.3 的文档中,有一个很好的 “设计背景” 部分,讨论了支持类型化消息发送和响应的困难。
Roland Kuhn 也发表了一篇很好的 NEScala 演讲,Akka Typed Channels: Implementing Type Calculations as Macros ([YouTube] / [Slides]),讨论了类型化通道的实现。
Akka 现已完全键入,根据文档。而无类型演员被称为“Akka Classic”...
例如,对于仅接受
Greet
消息的演员 (ref),您有
ActorRef<Greet> myactor;
(撰写本文时当前版本为 2.9.1。 不精通 Java 或 Scala,抱歉。)