如何将包含参数的案例类与通用类型匹配

Posted

技术标签:

【中文标题】如何将包含参数的案例类与通用类型匹配【英文标题】:How to match a Case Class containing a Parameter with Generic Type 【发布时间】:2021-12-03 13:58:31 【问题描述】:

我有一个有趣的问题,匹配 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
           ) 
      

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

现在,当我第一次尝试此代码时,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 的“询问”模式将 onReport Command 类中的 ActorRef 更改为 ActorRefAdapter,当然 ActorRefAdapter 是 ActorRef 的子类,但我不是确定我在将 ActorRef 映射到 TypeCase 的隐式中定义的内容可以处理这些东西,但我想不出一种方法来更改隐式以了解子类型....

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

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

谢谢解答...

【问题讨论】:

你试过private val Report = signal吗? Scala 在模式匹配中有一些关于字母大小写的硬编码启发式算法。 我提到的其他命令(正在匹配)也是用小写字母写的,但我会尝试 @posthumecaver 您的代码的某些部分不是有效的 Scala,例如final case class(key: Key, replyTo: ActorRef[ResponseBase[State]])。请准备 MCVE 或提供您的 build.sbt+imports 以使您的代码独立。 是的,你是对的,复制/粘贴是正确的,我修复了这个问题...我使用带有 gradle scala 插件的 IntelliJ Scala 插件开发了这个项目,所以我没有 sbt 文件,另外这是一个非常具体的项目,需要在 Docker 容器中使用 Cassandra,因此作为可复制案例转移有点困难,但我会尝试编写一个小项目来复制这个问题 嗨,这是一场小争执,但是在 main/test/scala 下有一个可重现的案例github.com/mehmetsalgar/scala_pattern_matching,如果您调试它并在 BaseActor 案例语句中设置一个断点,您会发现一个 FirstSpec 测试您会看到 onReport 命令不匹配。我试图尽可能简单地重现,所以 Akka Actor 可能没有太大意义,但你可以看到问题出在哪里...... 【参考方案1】:

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

您为什么决定用ClassTag 替换typT.cast(myClassOf)?这没有意义。

我猜你使用了类似"No default Typeable for parametrized type" using Shapeless 2.1.0-RC2

如果你的gole是让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 是一个类似非空容器的东西。但是ActorRef 就像一个函数T =&gt; Unit,所以不应该有一个Typeable 来代替它

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

  ...

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

【讨论】:

以上是关于如何将包含参数的案例类与通用类型匹配的主要内容,如果未能解决你的问题,请参考以下文章

Scala在通用特征方法中找不到案例类参数

如何使通用可变参数函数中先前声明的函数的返回类型成功进行上下文推断?

Java中的通用方法-如何将参数类类型作为返回类型返回[重复]

如何将数据组表示为通用 Typescript 接口

类型'Queryable'上的通用方法'OrderBy'与提供的类型参数不兼容

Rust宏接受类型与通用参数