Scala 2.13如何避免枚举特质子类的类型类模式匹配?
问题描述
在Scala 2.13中,为密封特质EnumType的所有子类实现了Operation类型类。当前getTypeClass函数需要随EnumType的修改手动更新模式匹配逻辑,希望能不依赖具体子类,动态获取对应Operation类型类实例,同时支持从JSON解码动态EnumType实例的场景。
现有代码
sealed trait EnumType case object Add10 extends EnumType case object Add50 extends EnumType trait Operation[+T] { def op(a: Int): Int } implicit val add10: Operation[Add10.type] = (a: Int) => a + 10 implicit val add50: Operation[Add50.type] = (a: Int) => a + 50 def getTypeClass(enumType: EnumType): Operation[EnumType] = { // 每次EnumType修改都要手动修改这段模式匹配 enumType match { case Add10 => implicitly[Operation[Add10.type]] case Add50 => implicitly[Operation[Add50.type]] } // 我希望不用模式匹配就能实现,比如下面这种写法(但无法编译): // implicitly[Operation[concrete_type_of(enumType)]] } // enumType的值是动态的,甚至可以从JSON文档解码得到 val enumType: EnumType = Add50 println(getTypeClass(enumType).op(10)) // 输出60
补充场景
希望无需显式指定EnumType子类即可调用,例如使用circe解码JSON的场景:
case class Doc(enumType: EnumType, param: Int) implicit val docDecoder: Decoder[Doc] = deriveDecoder implicit val enumTypeDecoder: Decoder[EnumType] = deriveEnumerationDecoder decode[Doc]("""{"enumType": "Add10", "param": 10}""").map { doc => println(getTypeClass(doc.enumType).op(doc.param)) }
解决方案
方案1:让EnumType自带Operation实例(推荐)
这种方式无需反射,每个EnumType子类自行提供对应的Operation实现,新增子类时只需实现抽象方法,彻底摆脱模式匹配:
sealed trait EnumType { // 抽象方法,每个子类返回自身对应的Operation实例 def operation: Operation[this.type] } case object Add10 extends EnumType { override def operation: Operation[Add10.type] = (a: Int) => a + 10 } case object Add50 extends EnumType { override def operation: Operation[Add50.type] = (a: Int) => a + 50 } trait Operation[+T] { def op(a: Int): Int } // 简化后的getTypeClass,直接调用实例方法 def getTypeClass(enumType: EnumType): Operation[EnumType] = { enumType.operation.asInstanceOf[Operation[EnumType]] } // 基础测试 val enumType: EnumType = Add50 println(getTypeClass(enumType).op(10)) // 输出60 // circe解码场景适配 import io.circe._, io.circe.generic.semiauto._, io.circe.parser._ case class Doc(enumType: EnumType, param: Int) implicit val enumTypeDecoder: Decoder[EnumType] = deriveEnumerationDecoder implicit val docDecoder: Decoder[Doc] = deriveDecoder decode[Doc]("""{"enumType": "Add10", "param": 10}""").map { doc => println(getTypeClass(doc.enumType).op(doc.param)) // 输出20 }
优势:
- 无模式匹配维护成本,新增子类时不会遗漏
- 无反射开销,运行时性能优异
- 完美支持动态实例(如JSON解码后的对象)
方案2:运行时反射实现(需scala-reflect依赖)
如果无法修改EnumType的定义,可以借助Scala运行时反射查找隐式Operation实例:
首先在build.sbt中添加依赖:
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
然后修改代码:
import scala.reflect.runtime.universe._ import scala.reflect.runtime.currentMirror sealed trait EnumType case object Add10 extends EnumType case object Add50 extends EnumType trait Operation[+T] { def op(a: Int): Int } implicit val add10: Operation[Add10.type] = (a: Int) => a + 10 implicit val add50: Operation[Add50.type] = (a: Int) => a + 50 def getTypeClass(enumType: EnumType): Operation[EnumType] = { val runtimeClass = enumType.getClass // 获取实例的运行时类型 val typeSymbol = currentMirror.classSymbol(runtimeClass) val instanceType = typeSymbol.toType // 构造Operation[具体子类]的类型 val operationType = typeOf[Operation[_]].typeConstructor.apply(instanceType) // 查找对应类型的隐式实例 val implicitInstance = currentMirror.implicitly(operationType) implicitInstance.asInstanceOf[Operation[EnumType]] } // 测试与解码场景同样适用 val enumType: EnumType = Add50 println(getTypeClass(enumType).op(10)) // 输出60
注意:
- 需确保所有
Operation隐式实例在当前作用域中可见 - 反射存在一定性能开销,适合对修改原有定义有严格限制的场景
内容的提问来源于stack exchange,提问作者Tomek L
相关产品推荐
相关产品推荐

