首页 > 解决方案 > 如何将包含参数的案例类与通用类型匹配

问题描述

我有一个有趣的问题,匹配 Scala 中的案例类......

我正在使用 Akka,并且我拥有将在系统中的每个 Actor 中使用的功能,因此为我的 Actor 创建了一个基类,我尝试在那里匹配该命令....

我的命令如下所示...

sealed trait ReportCommand extends ProcessCommand
final case class onReport(key: Key, replyTo: ActorRef[ResponseBase[State]]) extend ReportCommand

虽然我构造了 Base Class 以便它可以从不同的 Actor 中使用,但onReport作为通用参数传递给 Base Actor,以用于与案例类的模式匹配......

abstract class BaseActor[E: ClassTag, R <: ReportBase[STATE], COMMAND](signal: TypeCase[R]) {
    private val report = signal        

    def base[B <: E: ClassTag](cmd: E, state: STATE)(f: B => ReplyEffect[COMMAND, STATE]): ReplyEffect[COMMAND, STATE] = 
      cmd match {
        case report(report) => 
           Effect.reply(report.replytTo)(new ResponseBase[STATE]{
             override def state: STATE = state
           }) 
      }
}

首先,如果您认为这个构造不起作用,它会起作用,我有另一个命令(我没有放在这里),它在命令类中没有通用参数,上面的代码片段能够匹配那个片段。

现在,当我第一次尝试此代码时,Shapeless 抱怨 TypeCase 的 Typeable 没有到 ActorRef 的映射,所以在研究了互联网后,我发现我必须执行以下操作....

implicit def mapActorRef[T: ClassTag]: Typeable[ActorRef[T]] =
   new Typeable[ActorRef[T]] {
      private val typT = Typeable[T]

      override def cast(t: Any) : Option[ActorRef[T]] = {
         if(t==null) None
         else if(t.isInstanceOf[ActorRef[_]]) {
           val o= t.asInstanceOf[ActorRef[_]]
           for {
             _ <- typT.cast(myClassOf)
           } yield o.asInstanceOf[ActorRef[T]]
         } else None
      }
   }

def myClassOf[T: ClassTag] = implicitly[ClassTag[T]].runtimeClass

implicit def responseBaseIsTypeable[S: Typeable] : Typeable[ResponseBase[S]] =
   new Typeable[ResponseBase[S]] {
      private val typS = Typeable[S]
         
      override def cast(t: Any) : Option[ResponseState[S]] = {
         if(t==null) None
         else if(t.isIntanceOf[ResponseBase[_]]) {
            val o = t.asInstanceOf[ResponseBase[_]]
            for {
              _ <- typS.cast(o.state)
            } yield o.asInstanceOf[ResponseBase[S]]
         } else None
      }
   }

现在,在进行此更改后,我没有收到来自 Shapeless 的任何异常但case report(report)不匹配,我不知道我们如何从 Scala 那里得到推理,为什么它决定它不匹配。在我的调试会话中,我观察到以下内容。

我正在使用 Akka 的 Ask Pattern 与这个演员交流......

val future : Future[BaseActor.ResponseBase[Actor.State]] = actorRef.ask[BaseActor.ResponseBase[Actor.State]](ref =>
   Actor.onReport(key, ref)
)

现在,如果我观察 BaseActor 收到的 cmd 对象,我看到 Akka 的“询问”模式将onReportCommand 类中的 ActorRef 更改为 ActorRefAdapter,当然 ActorRefAdapter 是 ActorRef 的子类,但我不确定我在将 ActorRef 映射到 TypeCase 的隐式可以处理这些东西,但我想不出一种方法来更改隐式以了解子类型......

不幸的是,ActorRefAdapter 是私有的,package akka.actor.typed.internal.adapter所以我不能为 ActorRefAdapter 定义额外的映射。

所以任何人都可以看到为什么 Scala 不匹配我的 Shapeless <-> TypeCase 配置并给我一些提示......

谢谢回答...

标签: scalapattern-matchingakkashapeless

解决方案


您的实例Typeable[ActorRef[T]]不正确。

你为什么决定替换 a ClassTagin typT.cast(myClassOf)?这不可能有意义。

我猜你使用 Shapeless 2.1.0-RC2 使用了类似“参数化类型没有默认类型”之类的东西

如果您的目标是进行case report(replyTo)匹配,那么您可以定义

implicit def mapActorRef[T: Typeable]: Typeable[ActorRef[T]] =
  new Typeable[ActorRef[T]] {
    private val typT = Typeable[T]

    override def cast(t: Any): Option[ActorRef[T]] = {
      if (t == null) None
      else util.Try(t.asInstanceOf[ActorRef[T]]).toOption
    }

    override def describe: String = s"ActorRef[${typT.describe}]"
  }

问题是这个实例也很糟糕。现在case report(replyTo)匹配太多了。

val actorTestKit = ActorTestKit()
val replyToRef = actorTestKit.spawn(ReplyToActor(), "replyTo")

import BaseActor._ // importing implicits
import shapeless.syntax.typeable._
val future: Future[BaseActor.ResponseBase[Actor.State]] = replyToRef.cast[ActorRef[Int]].get.ask[BaseActor.ResponseBase[Actor.State]](ref =>
  1
)(5.seconds, system.scheduler)

Await.result(future, 10.seconds) // ClassCastException

类型类的合法实例Typeable不能为每种类型定义。

为多态类型(定义明确)提供实例(具体实例化)几乎是Typeable这里和 Haskell 中的全部要点。

上面的关键词是“定义明确的地方”。在类似非空容器的情况下,它的定义很好。对于函数值,它显然没有很好地定义。

https://github.com/milessabin/shapeless/issues/69

ResponseBase是一个类似非空容器的东西。butActorRef就像一个函数T => Unit,所以不应该有Typeablefor 它

trait ActorRef[-T] extends ... {
  def tell(msg: T): Unit

  ...
}

你应该重新考虑你的方法。


推荐阅读