开发者

How to get rid of downcast using synchronous send with Scala Actors?

开发者 https://www.devze.com 2023-03-13 20:26 出处:网络
Suppose, I send a request to an actor and receive its response synchronously: case class MyRequest() case class MyResponse(data:Any)

Suppose, I send a request to an actor and receive its response synchronously:

case class MyRequest()
case class MyResponse(data:Any)

val resp = myActor !? MyRequest()

Now I have to downcast resp to MyResponse to access data

val data = (resp.asInstanceOf[MyResponse]).data

How can I g开发者_运维知识库it rid of the casting and write the code in a type safe manner? (I guess I can use pattern matching instead but I would prefer another solution).


As far as I know Scala Actors aren´t typed. So use Akka actors, which support typed actors. I also encountered your problem before and have handeled it with implicit manifest to get some kind of typed actors in scala.


Actors are not typed, so there's no way of doing it with the core library. However, there are several ways to create utility methods to it.

You can encode the result type in the message and then have a utility method that uses that:

trait Result[T]

class RichActor(self: Actor) {
    def !?![T](msg: Result[T]): T = (self !? msg).asInstanceOf[T]

    def !?![T](timeout: Long, msg: Result[T]) = (self.!?(timeout, msg)).asInstanceOf[Option[T]]
}

implicit def enrichActor(a: Actor) = new RichActor(a)

usage:

case class Message() extends Result[Int]

val i = actor !?! Message()

Type of i is an Int

Note: previously posted here: http://www.tikalk.com/java/blog/type-safe-actor-messages


Pattern matching is the normal way to get data out of messages and to distinguish which type of message you received.

I would just use pattern matching here, and am curious why you would prefer another solution.


As of this comment to my previous answer I want to share my solution with manifests, using akka actors:

import akka.actor._

trait Message
case class Foo(i: Int) extends Message
case class Bar(b: Boolean) extends Message
case class Baz(s: String) extends Message

case class MSG[C](content: C)(implicit m: Manifest[C]) {
  def manifest = m
  @inline def asMsg[C] = this.asInstanceOf[MSG[C]]
}

class MyActor[C](implicit manifest: Manifest[C]) extends Actor {
  private val behaviour: C =*> Unit = {
    case foo @ Foo(i) => println(foo) // i: Int
    case bar @ Bar(b) => println(bar) // b: Boolean
  }
  def receive = {
    case m: MSG[_] if m.manifest <:< implicitly[Manifest[C]] && behaviour.isDefinedAt(m.asMsg[C].content) => behaviour(m.asMsg[C].content)
    case m => println("wrong message : " + m)
  }
}

val system = ActorSystem("MySystem")
val myActor = system.actorOf(Props(new MyActor[Message]()), name = "myactor")
myActor ! MSG(Foo(1)) //  Foo(1)
myActor ! MSG(Bar(true)) // Bar(true)
myActor ! MSG(Baz("!")) // wrong message : MSG(Baz(!))
myActor ! Foo(2) // wrong message : Foo(2)
myActor ! MSG("?") // wrong message : MSG(?)
system.shutdown

Furthermore I tried to use this with parameterized types:

sealed trait Parameterized1[X] // only marker
case class Foo1[X](x: X) extends Parameterized1[X]

case class MSG1[X, Z[X]](content: Z[X])(implicit mX: Manifest[X]) {
  def manifestX = mX
  @inline def asMsg[A, C[A]] = this.asInstanceOf[MSG1[A, C]]
}

class MyActor1[X, Z[X]](implicit mX: Manifest[X]) extends Actor {
  private val behaviour: Z[X] =*> Unit = {
    case foo @ Foo1(x: X) => println(foo)
  }
  def receive = {
    case m: MSG1[_, _] if m.manifestX <:< implicitly[Manifest[X]] && behaviour.isDefinedAt(m.asMsg[X, Z].content) => behaviour(m.asMsg[X, Z].content)
    case m => println("wrong message : " + m)
  }
}

val system1 = ActorSystem("MySystem1")
val myActor1 = system1.actorOf(Props(new MyActor1[Int, Parameterized1]()), name = "myactor1")
myActor1 ! MSG1(Foo1(1)) // Foo1(1)
myActor1 ! MSG1(Foo1("A")) // wrong message : MSG1(Foo1(A))
myActor1 ! Foo1(1) // wrong message : Foo1(1)
system1.shutdown

Or even with two type parameters:

sealed trait Parameterized2[X, Y] // only marker
case class Foo2[X, Y](x: X, y: Y) extends Parameterized2[X, Y]
case class Bar2[X, Y](x: X, y: Y) extends Parameterized2[X, Y]

case class MSG2[X, Y, Z[X, Y]](content: Z[X, Y])(implicit mX: Manifest[X], mY: Manifest[Y]) {
  def manifestX = mX
  def manifestY = mY
  @inline def asMsg[A, B, C[A, B]] = this.asInstanceOf[MSG2[A, B, C]]
}

class MyActor2[X, Y, Z[X, Y]](implicit mX: Manifest[X], mY: Manifest[Y]) extends Actor {
  private val behaviour: Z[X, Y] =*> Unit = {
    case foo @ Foo2(x: X, y: Y) => println(foo)
    case bar @ Bar2(x: X, y: Y) => println(bar)
  }
  def receive = {
    case m: MSG2[_, _, _] if m.manifestX <:< implicitly[Manifest[X]] && m.manifestY <:< implicitly[Manifest[Y]] && behaviour.isDefinedAt(m.asMsg[X, Y, Z].content) => behaviour(m.asMsg[X, Y, Z].content)
    case m => println("wrong message : " + m)
  }
}

val system2 = ActorSystem("MySystem2")
val myActor2 = system2.actorOf(Props(new MyActor2[Int, String, Parameterized2]()), name = "myactor2")
myActor2 ! MSG2(Foo2(1, "A")) // Foo2(1,A)
myActor2 ! MSG2(Foo2(1, true)) // wrong message : MSG2(Foo2(1,true))
myActor2 ! Foo2(1, "A") // wrong message : Foo2(1,A)
myActor2 ! MSG2((1, "A")) // wrong message : MSG2((1,A))
myActor2 ! MSG2(Bar2(2, "B")) // Bar2(2,B)
system2.shutdown
0

精彩评论

暂无评论...
验证码 换一张
取 消