[FIXED] Geben Sie eine Klasseninstanz für Fallobjekte ein, die in einer versiegelten Eigenschaft definiert sind

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 getTypeClassFunktion nicht von konkreten Typen abhängt, die EnumType. Jetzt muss ich diese Funktion jederzeit EnumTypeaufrufen und Muster hinzufügen oder entfernen. Gibt es eine Möglichkeit, Instanzen der OperationTypklasse für EnumTypeTypen 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 EnumTypeverwenden ( circein 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 enumTypenur den Typ hat EnumTypeund 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 Add50statisch 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?

Wie erhält man in einem Scala-Makro den vollständigen Namen, den eine Klasse zur Laufzeit haben wird?


Beantwortet von –
Dmytro Mitin


Antwort geprüft von –
Marilyn (FixError Volunteer)

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like