简体   繁体   中英

How to accept only a specific subtype of existential type?

Consider the following implementation sketch:

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 = ???

The problem:

Having the case class containing a field of an existential type is it possible to implement a method that would accept the only branch of the existential type.

Maybe shapeless could be helpful here?

First of all, Data.Aux[T] forSome { type T <: Type} can be written as Data.Aux[_] and is just Data

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

If you put println inside handleMainType2

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)))

then

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

will print one of the following (depending on how you define the type of Main parameter: Data.Aux[T] forSome { type T <: Type} , Data.Aux[_] or 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())

both times. So inside method handleMainType2 main.data has type just Data and Data1 / Data2 are indistinguishable by type. What is distinguishable is runtime class:

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

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

So you can define

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

with runtime behavior.

If you want compile-time behavior then you can try to make handleMainType2 a macro and use runtime reflection in the macro

// 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

You can even make the macro implicit if you prefer not to make handleMainType2 a macro itself.

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

Notice that handleMainType2(Main(Data2("a"))) or even

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

will not work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM