简体   繁体   中英

How to determine whether a type parameter is a subtype of a trait?

Let's say I have the following types

class Foo
trait Bar

Is there a way to make a method which takes in a Type parameter, T, and determine if that T is a Bar? For example,

def isBar[T <: Foo: Manifest] = 
  classOf[Bar].isAssignableFrom(manifest[T].erasure)

Sadly, isBar[Foo with Bar] is false because erasure seems to erase mixins.

Also, manifest[Foo with Bar] <:< manifest[Bar] is false

Is this possible at all?

I looked at this question: How to tell if a Scala reified type extends a certain parent class?

but that answer doesn't work with mixed-in traits as they seem to be erased as evidenced above.

This can be achieved with TypeTags (at least 2.10M7):

scala> class Foo; trait Bar
defined class Foo
defined trait Bar

scala> import reflect.runtime.universe._
import reflect.runtime.universe._

scala> def isBar[A <: Foo : TypeTag] = typeOf[A].baseClasses.contains(typeOf[Bar].typeSymbol)
isBar: [A <: Foo](implicit evidence$1: reflect.runtime.universe.TypeTag[A])Boolean

scala> isBar[Foo]
res43: Boolean = false

scala> isBar[Foo with Bar]
res44: Boolean = true

TypeTags provide a 1:1 translation of Scala types because they represent the types the compiler knows. Therefore they are much more powerful than plain old Manifests:

scala> val fooBar = typeTag[Foo with Bar]
fooBar: reflect.runtime.universe.TypeTag[Foo with Bar] = TypeTag[Foo with Bar]

With the method tpe we get full access to Scalas new Reflection:

scala> val tpe = fooBar.tpe // equivalent to typeOf[Foo with Bar]
tpe: reflect.runtime.universe.Type = Foo with Bar

scala> val tpe.<tab><tab> // lot of nice methods here
=:=                 asInstanceOf        asSeenFrom          baseClasses         baseType            contains            declaration         
declarations        erasure             exists              find                foreach             isInstanceOf        kind                
map                 member              members             narrow              normalize           substituteSymbols   substituteTypes     
takesTypeArgs       termSymbol          toString            typeConstructor     typeSymbol          widen  

It is possible to do this pre-2.10, just not (as far as I know) with manifests:

def isBar[T <: Foo](implicit ev: T <:< Bar = null) = ev != null

It's a bit of a hack, but it works as desired.

scala> isBar[Foo with Bar]
res0: Boolean = true

scala> isBar[Foo]
res1: Boolean = false

You can resolve it without reflection by using typeclasses:

trait IsBar[T] {
  def apply():Boolean
}

trait LowerLevelImplicits {
  implicit def defaultIsBar[T] = new IsBar[T]{
    def apply() = false
  }
}

object Implicits extends LowerLevelImplicits {
  implicit def isBarTrue[T <: Bar] = new IsBar[T] {
    def apply() = true
  }
}

def isBar[T<:Foo]( t: T )( implicit ib: IsBar[T] ) = ib.apply()

scala> import Implicits._

scala> isBar( new Foo )
res6: Boolean = false

scala> isBar( new Foo with Bar )
res7: Boolean = true

Another typeclass usage(more generic):

  trait SubClassGauge[A, B] {
    def A_isSubclassOf_B: Boolean
  }

  implicit class IsSubclassOps[A](a: A) {
    def isSubclassOf[B](implicit ev: SubClassGauge[A, B]): Boolean = ev.A_isSubclassOf_B
  }

  trait LowerLevelImplicits {
    implicit def defaultSubClassGauge[A, B] = new SubClassGauge[A, B] {
      override def A_isSubclassOf_B: Boolean = false
    }
  }

  object Implicits extends LowerLevelImplicits {
    implicit def subClassGauge[A <: B, B]: SubClassGauge[A, B] = new SubClassGauge[A, B] {
      override def A_isSubclassOf_B: Boolean = true
    }
  }

  trait Prime
  class NotSuper
  class Super extends Prime
  class Sub extends Super
  class NotSub

Now, in REPL:

@ import Implicits._ 
import Implicits._
@ (new Sub).isSubclassOf[NotSuper] 
res29: Boolean = false
@ (new Sub).isSubclassOf[Super] 
res30: Boolean = true
@ (new Sub).isSubclassOf[Prime] 
res31: Boolean = true
@ (new Super).isSubclassOf[Prime] 
res32: Boolean = true
@ (new Super).isSubclassOf[Sub] 
res33: Boolean = false
@ (new NotSub).isSubclassOf[Super] 
res34: Boolean = false

TypeTag now belongs to scala reflect package. One needs to add extra dependency to use it.

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