Ausgabe
In Scala 2.13 habe ich einen Fall, in dem ich eine Operation für alle Typen definiere, die ein versiegeltes Merkmal erweitern EnumType
. Ich habe es zum Laufen gebracht, aber ich möchte, dass die getTypeClass
Funktion nicht von konkreten Typen abhängt, die EnumType
. Jetzt muss ich diese Funktion jederzeit EnumType
aufrufen und Muster hinzufügen oder entfernen. Gibt es eine Möglichkeit, Instanzen der Operation
Typklasse für EnumType
Typen zu erhalten, aber ohne Musterabgleich für alle?
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
BEARBEITEN So wünschte ich, es würde aufgerufen, ohne explizite Untertypen von zu EnumType
verwenden ( circe
in diesem Beispiel zum Decodieren von 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))
}
Lösung
Da Sie nur wissen, dass statisch enumType
nur den Typ hat EnumType
und basierend auf Laufzeitwert/Laufzeitklasse von übereinstimmen möchten enumType
, müssen Sie eine Art Reflektion verwenden:
- entweder Laufzeitkompilierung mit Reflective Toolbox (Auflösung von Impliziten zur Laufzeit)
// 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]]
oder
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]]
oder
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)
}
- oder ein Makro (Automatisierung des Musterabgleichs)
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]]
//}
Da alle Objekte zur Kompilierzeit definiert werden, denke ich, dass ein Makro besser ist.
Zuordnung der kovarianten Fallklasse zu ihrer Basisklasse ohne Typparameter und zurück
Abrufen von Unterklassen eines versiegelten Merkmals
Iteration über ein versiegeltes Merkmal in Scala?
Sie können sogar eine Makro -Whitebox erstellen und dann mithilfe der Laufzeitreflexion im Makro Add50
statisch eingeben (wenn die Klasse während der Makroerweiterung bekannt ist).
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
Wie akzeptiert man nur einen bestimmten Untertyp des existentiellen Typs?
Beantwortet von – Dmytro Mitin
Antwort geprüft von – Marilyn (FixError Volunteer)