例えばこんな方針で抽象化を試みる。
- Actorを継承したtraitを作って、共通処理を抽象化する。
- 作ったtraitは型パラメータを持たせて、具象クラス化するときに例えば受け取るメッセージの型を指定する。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import akka.actor._ | |
trait AbstractActor[MesType] extends Actor { | |
override def receive = ... | |
} | |
case class MyMessage(mes: String) | |
class MyActor extends AbstractActor[MyMessage] |
なんでわざわざ型パラメータなんて持たせてるかというと、これをやりたい。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import akka.actor._ | |
import scalaz._, Scalaz._ | |
trait AbstractActor[MesType] extends Actor { | |
implicit val MesShow: Show[MesType] | |
override def receive = { | |
case m: MesType => m.println // コンパイルエラーだけどこういうことがやりたい | |
} | |
} | |
case class MyMessage(mes: String) | |
class MyActor extends AbstractActor[MyMessage] { | |
override val MesShow = Show.showA[MyMessage] | |
} |
要するに、送受信するメッセージは処理を内包しないただの型(case class MyMessage)にしといて、その型の挙動は型クラスのインスタンス(MesShow)で制御したいというわけ。
ところが、この実装には問題がある(実際に上記コードはコンパイルが通らない)。
- akkaのActorにおいてメッセージはAny型で渡されるのでRuntimeにしか型判定できない
- traitの型パラメータはコンパイル時に型消去されるのでRuntimeでの判定には使えない
=> つまり、上記コードの「case m: MesType」という判定はできない、となる。
これを解決するためには、
- Anyで渡ってくるメッセージはobj.getClassを経由してRuntimeにクラス情報を取得する
- 型パラメータの方は、TypeTagを渡すことでRuntimeまでクラス情報を引き継ぐ
とやるしかない、と思う。たぶん。
で、書いてみたのが以下のコード。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import akka.actor._ | |
import scalaz._, Scalaz._ | |
import scala.reflect.runtime.universe._ | |
trait AbstractActor[MesType] extends Actor { | |
implicit val TTag: TypeTag[MesType] | |
implicit val MesShow: Show[MesType] | |
override def receive = { | |
case m if getType(m) <:< typeOf[MesType] => { | |
val mes = m.asInstanceOf[MesType] | |
mes.println | |
} | |
} | |
def getType(obj: Any): Type = { | |
val clazz = obj.getClass | |
val mirror = runtimeMirror(clazz.getClassLoader) | |
mirror.classSymbol(clazz).toType | |
} | |
} | |
case class MyMessage(mes: String) | |
class MyActor extends AbstractActor[MyMessage] { | |
override val TTag = typeTag[MyMessage] | |
override val MesShow = Show.showA[MyMessage] | |
} |
TypeTagをわざわざ指定してやらないといけないところとか、asInstanceOfでダウンキャストしてるところとか、カッコ悪い。
というわけで、「こうやるともっと綺麗に書けるよ」とか、「そもそも方針としてこうやるべき」とかあったら教えてください!