scala 在Seed特征中定义的Case对象的类型类示例

3qpi33ja  于 2022-11-09  发布在  Scala
关注(0)|答案(2)|浏览(159)

在Scala2.13中,我为所有类型定义了一些操作,扩展了一些密封的特征EnumType。我让它工作了,但我希望getTypeClass函数不依赖于扩展EnumType的具体类型。现在,每当EnumType发生更改并添加或删除某些模式时,我都必须访问此函数。有没有一种方法可以获得EnumType类型的Operation类型类的示例,而不需要对所有类型进行模式匹配?

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] = {
    // I need to modify this pattern matching
    // every time EnumType changes
    enumType match {
      case Add10 => implicitly[Operation[Add10.type]]
      case Add50 => implicitly[Operation[Add50.type]]
    }

    // I'd wish it could be done with without pattern matching like (it does not compile):
    //   implicitly[Operation[concrete_type_of(enumType)]]
  }

  // value of enumType is dynamic - it can be even decoded from some json document
  val enumType: EnumType = Add50
  println(getTypeClass(enumType).op(10)) // prints 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).call(doc.param))
  }
waxmsbnn

waxmsbnn1#

由于您只知道静态enumType只有EnumType类型,并且希望基于enumType的运行时值/运行时类进行匹配,因此您必须使用某种反射:

  • 使用反射工具箱进行运行时编译(在运行时解析隐含)
// libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val tb = cm.mkToolBox()

def getTypeClass(enumType: EnumType): Operation[EnumType] =
  tb.eval(q"_root_.scala.Predef.implicitly[Operation[${cm.moduleSymbol(enumType.getClass)}]]")
    .asInstanceOf[Operation[EnumType]]

def getTypeClass(enumType: EnumType): Operation[EnumType] =
  tb.eval(tb.untypecheck(tb.inferImplicitValue(
    appliedType(
      typeOf[Operation[_]].typeConstructor,
      cm.moduleSymbol(enumType.getClass).moduleClass.asClass.toType
    ),
    silent = false
  ))).asInstanceOf[Operation[EnumType]]

def getTypeClass(enumType: EnumType): Operation[EnumType] = {
  val cases = typeOf[EnumType].typeSymbol.asClass.knownDirectSubclasses.map(subclass => {
    val module = subclass.asClass.module
    val pattern = pq"`$module`"
    cq"$pattern => _root_.scala.Predef.implicitly[Operation[$module.type]]"
  })
  tb.eval(q"(et: EnumType) => et match { case ..$cases }")
    .asInstanceOf[EnumType => Operation[EnumType]]
    .apply(enumType)
}
  • macro(自动模式匹配)
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def getTypeClass(enumType: EnumType): Operation[EnumType] = macro getTypeClassImpl

def getTypeClassImpl(c: blackbox.Context)(enumType: c.Tree): c.Tree = {
  import c.universe._
  val cases = typeOf[EnumType].typeSymbol.asClass.knownDirectSubclasses.map(subclass => {
    val module = subclass.asClass.module
    val pattern = pq"`$module`"
    cq"$pattern => _root_.scala.Predef.implicitly[Operation[$module.type]]"
  })
  q"$enumType match { case ..$cases }"
}

//scalac: App.this.enumType match {
//  case Add10 => _root_.scala.Predef.implicitly[Macro.Operation[Add10.type]]
//  case Add50 => _root_.scala.Predef.implicitly[Macro.Operation[Add50.type]]
//}

因为所有对象都是在编译时定义的,所以我想宏更好。
Covariant case class mapping to its base class without a type parameter and back
Getting subclasses of a sealed trait
Iteration over a sealed trait in Scala?
您甚至可以创建一个宏白盒,然后在宏中使用runtime reflection可以静态地使用Add50类型(如果在宏展开时知道类)

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

def getTypeClass(enumType: EnumType): Operation[EnumType] = macro getTypeClassImpl

def getTypeClassImpl(c: whitebox.Context)(enumType: c.Tree): c.Tree = {
  import c.universe._
  val clazz = c.eval(c.Expr[EnumType](c.untypecheck(enumType))).getClass
  val rm = scala.reflect.runtime.currentMirror
  val symbol = rm.moduleSymbol(clazz)
  //q"_root_.scala.Predef.implicitly[Operation[${symbol.asInstanceOf[ModuleSymbol]}.type]]" // implicit not found
  //q"_root_.scala.Predef.implicitly[Operation[${symbol/*.asInstanceOf[ModuleSymbol]*/.moduleClass.asClass.toType.asInstanceOf[Type]}]]" // implicit not found
    // "migrating" symbol from runtime universe to macro universe
  c.parse(s"_root_.scala.Predef.implicitly[Operation[${symbol.fullName}.type]]")
}
object App {
  val enumType: EnumType = Add50
}
val operation = getTypeClass(App.enumType)
operation: Operation[Add50.type] // not just Operation[EnumType]
operation.op(10) // 60

How to accept only a specific subtype of existential type?
In a scala macro, how to get the full name that a class will have at runtime?

iq0todco

iq0todco2#

def getTypeClass[T <: EnumType : Operation](t: T) = implicitly[Operation[T]]

 println(getTypeClass(Add50).op(10))

事实上,您甚至根本不需要getTypeClass

def operate[T <: EnumType : Operation](t: T)(param: Int) = implicitly[Operation[T]].op(param)

println(operate(Add50)(10))

我在上面使用的Foo : Bar表示法相当于:

def operate[T <: EnumType](t: T)(param: Int)(op: Operation[T]) = op.op(param)

请注意,您实际上并没有在任何地方使用Add50Add10的示例。它们可能只是特征而不是物体(IMO,这更能反映意图):

sealed trait EnumType
  trait Add10 extends EnumType
  trait Add50 extends EnumType

  trait Operation[+T] {
    def op(a: Int): Int
  }

  implicit val add10: Operation[Add10] = (a: Int) => a + 10
  implicit val add50: Operation[Add50] = (a: Int) => a + 50

  def operate[T <: EnumType : Operation](param: Int) = 
   implicitly[Operation[T]].op(param)

 println(operate[Add50](10))

相关问题