Scala 3中Avro4s为父类Event创建解码器的实现问题
解决方案
修改你的函数,通过map方法将子类型的Decoder转换为Decoder[Event],这是类型安全的最优方案:
import com.sksamuel.avro4s.Decoder trait Event case class UserCreated(age: Int) extends Event case class UserDeleted(age: Int) extends Event def getDecoderBasedOnType(eventName: String): Decoder[Event] = eventName match { case "usercreated" => Decoder[UserCreated].map(identity[Event]) case "userdeleted" => Decoder[UserDeleted].map(identity[Event]) }
也可以利用Scala自动向上转型的特性,写更简洁的版本:
def getDecoderBasedOnType(eventName: String): Decoder[Event] = eventName match { case "usercreated" => Decoder[UserCreated].map(_ => _) case "userdeleted" => Decoder[UserDeleted].map(_ => _) }
如果确认类型绝对安全,也可以用显式类型转换(不推荐,易引发运行时风险):
def getDecoderBasedOnType(eventName: String): Decoder[Event] = eventName match { case "usercreated" => Decoder[UserCreated].asInstanceOf[Decoder[Event]] case "userdeleted" => Decoder[UserDeleted].asInstanceOf[Decoder[Event]] }
原理指引
问题的核心在于Scala的类型方差规则和Avro4s Decoder的类型定义:
Decoder的逆变特性:Avro4s的
Decoder是逆变类型构造器(定义为Decoder[-T])。逆变的核心规则是:若A <: B(A是B的子类),则Decoder[B] <: Decoder[A](父类型的Decoder是子类型Decoder的子类)。- 对你的场景而言,
UserCreated <: Event,因此Decoder[Event]是Decoder[UserCreated]的子类,而非反过来。这意味着你不能直接将Decoder[UserCreated]赋值给Decoder[Event]类型的变量——子类型变量无法接收超类型实例。
- 对你的场景而言,
编译器的类型推断逻辑:当你在match分支返回
Decoder[UserCreated]和Decoder[UserDeleted]时,编译器会推断两者的共同超类型为Decoder[_ >: UserCreated with UserDeleted <: Event]。这个存在类型表示“Decoder的类型参数是UserCreated和UserDeleted的共同父类,且是Event的子类”,但它不等于Decoder[Event],因此无法匹配你声明的返回类型。map方法的转换作用:
Decoder的map方法签名为def map[B](f: T => B): Decoder[B]。当你传入identity[Event](即UserCreated => Event的向上转型函数)时,会将Decoder[UserCreated]安全转换为Decoder[Event]——既保证了解码后的实例能正确转为Event类型,又得到了期望的返回类型。
内容的提问来源于stack exchange,提问作者ticofab

