在 Scala 2.13 中,我有一個案例,我為所有擴展了一些 seal trait 的型別定義了一些操作EnumType。我讓它作業,但我希望getTypeClass函式不依賴于擴展的具體型別EnumType。EnumType現在我必須在任何時間更改并添加或洗掉一些模式時訪問此功能。有沒有辦法獲取型別的Operation型別類的實體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] = {
// 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))
}
uj5u.com熱心網友回復:
由于您只知道靜態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)
}
- 或宏(自動模式匹配)
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]]
//}
由于所有物件都是在編譯時定義的,我猜宏更好。
協變案例類映射到其基類,沒有型別引數并回傳
獲取密封特征的子類
迭代Scala中的密封特征?
您甚至可以制作一個宏whitebox,然后在宏中使用運行時反射,您可以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
如何只接受存在型別的特定子型別?
在 scala 宏中,如何獲取類在運行時的全名?
uj5u.com熱心網友回復:
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)
請注意,您實際上并沒有在任何地方使用實體。它們可能只是特征而不是物件(IMO,更好地反映了意圖):Add50Add10
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))
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/512156.html
