[FIXED] Wie akzeptiert man nur einen bestimmten Untertyp des existentiellen Typs?

Ausgabe

Betrachten Sie die folgende Implementierungsskizze:

sealed trait Type

object Type {
  case object Type1 extends Type
  case object Type2 extends Type
}

sealed trait Data {
  type T <: Type
}
object Data {
  type Aux[TT] = Data {
    type T = TT
  }

  case class Data1(i: Int) extends Data {
    type T = Type1.type
  }

  case class Data2(s: String) extends Data {
    type T = Type2.type
  }
}

case class Main(
  //lots of other fields
  data: Data.Aux[T] forSome { type T <: Type}
)

// This method is supposed to accept the only Main's
// that have data of type Data.Aux[Type2.type]
def handleMainType2(
  main: Main
): Unit = ???

Das Problem:

Da case classes ein Feld eines existentiellen Typs enthält, ist es möglich, eine Methode zu implementieren, die die einzige Verzweigung des existenziellen Typs akzeptieren würde.

Vielleicht könnte Shapeless hier hilfreich sein?

Lösung

Zunächst einmal Data.Aux[T] forSome { type T <: Type}kann so geschrieben werden Data.Aux[_]und ist gerechtData

implicitly[(Data.Aux[T] forSome { type T <: Type}) =:= Data] // compiles
implicitly[Data =:= (Data.Aux[T] forSome { type T <: Type})] // compiles

Wenn Sie printlnhineinlegenhandleMainType2

import scala.reflect.runtime.universe.{Type, TypeTag, typeOf}
def getType[A: TypeTag](a: A): Type = typeOf[A]

def handleMainType2(main: Main): Unit = 
  println(getType(main.data) + "=" + showRaw(getType(main.data)))

dann

handleMainType2(Main(Data1(1)))
handleMainType2(Main(Data2("a")))

druckt eines der folgenden (je nachdem, wie Sie den MainParametertyp definieren: Data.Aux[T] forSome { type T <: Type}, Data.Aux[_]oder Data)

App.Data{type T = T}=RefinedType(List(TypeRef(ThisType(App), App.Data, List())), Scope(TypeName("T")))
App.Data{type T = _$1}=RefinedType(List(TypeRef(ThisType(App), App.Data, List())), Scope(TypeName("T")))
App.Data=TypeRef(ThisType(App), App.Data, List())

beide Male. Inside-Methoden handleMainType2 main.datahaben also nur den Typ Dataund Data1/ Data2sind nach Typ nicht unterscheidbar. Was zu unterscheiden ist, ist die Laufzeitklasse:

def handleMainType2(main: Main): Unit =
  println(main.data.getClass)

//class App$Data$Data1
//class App$Data$Data2

So können Sie definieren

def handleMainType2(main: Main): Unit =
  assert(main.data.getClass.isAssignableFrom(classOf[Data2]))

mit Laufzeitverhalten.

Wenn Sie ein Verhalten zur Kompilierzeit wünschen, können Sie versuchen, handleMainType2ein Makro zu erstellen und die Laufzeitreflexion im Makro zu verwenden

// in a different subproject
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def handleMainType2(main: Main): Unit = macro handleMainType2Impl

def handleMainType2Impl(c: blackbox.Context)(main: c.Tree): c.Tree = {
  import c.universe._
  val clazz = c.eval(c.Expr[Main](c.untypecheck(main))).data.getClass
  if (clazz.isAssignableFrom(classOf[Data2]))
    q"()"
  else c.abort(c.enclosingPosition, s"${clazz.getName} <:!< Data2")
}
handleMainType2(Main(Data1(1))) // doesn't compile
handleMainType2(Main(Data2("a"))) // compiles

Sie können das Makro sogar implizit machen, wenn Sie es vorziehen, selbst handleMainType2kein Makro zu erstellen.

trait IsData2[D <: Data with Singleton]

object IsData2 {
  implicit def mkIsData2[D <: Data with Singleton]: IsData2[D] = macro mkIsData2Impl[D]

  def mkIsData2Impl[D <: Data with Singleton : c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._

    val clazz = c.eval(c.Expr[ValueOf[D]](c.untypecheck(
      c.inferImplicitValue(weakTypeOf[ValueOf[D]], silent = false)
    ))).value.getClass

    if(clazz.isAssignableFrom(classOf[Data2]))
      q"new IsData2[${weakTypeOf[D]}] {}"
    else c.abort(c.enclosingPosition, s"${weakTypeOf[D]} <:!< Data2")
  }
object App {
  val m1: Main = Main(Data1(1))
  val m2: Main = Main(Data2("a"))
}
def handleMainType2(main: Main)(implicit ev: IsData2[main.data.type]) = ()

handleMainType2(App.m1) // doesn't compile
handleMainType2(App.m2) // compiles

Beachten Sie das handleMainType2(Main(Data2("a")))oder sogar

val m2: Main = Main(Data2("a"))
handleMainType2(m2) 

wird nicht funktionieren.


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