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 class
es 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 println
hineinlegenhandleMainType2
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 Main
Parametertyp 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.data
haben also nur den Typ Data
und Data1
/ Data2
sind 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, handleMainType2
ein 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 handleMainType2
kein 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)