Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use the new reflection API to tell if the component type of an array conforms to a type parameter?

If I have a method...

def arrayConformsTo[A](as: Array[_]) = ???

... where I can add Context Bounds to A as needed. I want this method to look at the component type of the Array and return true if this is a subtype of A. So, for example:

arrayConformsTo[Int](Array(1, 2, 3)) //returns true

arrayConformsTo[String](Array(1, 2, 3)) //returns false

Prior to 2.10, this would have been done as follows:

def arrayConformsTo[A: Manifest](as: Array[_]) = 
  ClassManifest.fromClass(as.getClass.getComponentType) <:< manifest[A]

However this now compiles with deprecation warnings

<console>:8: warning: method <:< in trait ClassManifestDeprecatedApis is deprecated: Use scala.reflect.runtime.universe.TypeTag for subtype checking instead
       ClassManifest.fromClass(as.getClass.getComponentType) <:< manifest[A]
                                                         ^
<console>:8: warning: value ClassManifest in object Predef is deprecated: Use scala.reflect.ClassTag instead
       ClassManifest.fromClass(as.getClass.getComponentType) <:< manifest[A]

My first guess at this is as follows:

scala> def arrayConformsTo[A: reflect.ClassTag](as: Array[_]) =
     | reflect.ClassTag(as.getClass.getComponentType) <:< implicitly[reflect.ClassTag[A]]

But this gives a deprecation warning as well

<console>:8: warning: method <:< in trait ClassManifestDeprecatedApis is deprecated: Use scala.reflect.runtime.universe.TypeTag for subtype checking instead
       reflect.ClassTag(as.getClass.getComponentType) <:< implicitly[reflect.ClassTag[A]]
                                                  ^

It tells me to use TypeTag. But how? Is this even a valid thing to ask of reflection?


Appendix: this seems to work reasonably well for what I need, although it does not work for AnyVal:

scala> def arrayConformsTo[A: reflect.ClassTag](as: Array[_]) =
     | implicitly[reflect.ClassTag[A]].runtimeClass isAssignableFrom as.getClass.getComponentType
like image 930
oxbow_lakes Avatar asked May 29 '13 12:05

oxbow_lakes


2 Answers

The scala reflection api sure is quite a labyrinth, but at least it is comprehensive:

import scala.reflect.runtime.{universe => ru}
def arrayConformsTo[A: ru.TypeTag](as: Array[_]) = {
  val mirror = ru.runtimeMirror( getClass.getClassLoader )
  val classSym = mirror.classSymbol( as.getClass.getComponentType )
  classSym.toType <:< implicitly[ru.TypeTag[A]].tpe
}

REPL test:

scala> arrayConformsTo[Float]( Array[Float]() )
res9: Boolean = true

scala> arrayConformsTo[Int]( Array[Float]() )
res10: Boolean = false

scala> arrayConformsTo[AnyVal]( Array[Float]() )
res11: Boolean = true

scala> arrayConformsTo[AnyVal]( Array[Float]() )
res12: Boolean = true

scala> arrayConformsTo[Any]( Array[Float]() )
res13: Boolean = true

scala> arrayConformsTo[Any]( Array[Float]() )
res14: Boolean = true

scala> arrayConformsTo[AnyRef]( Array[Float]() )
res15: Boolean = false

scala> arrayConformsTo[AnyRef]( Array[Float]() )
res16: Boolean = false

(This edit by the OP for reasons of completeness)

Another solution (not requiring scala-reflect.jar), albeit one which does not preserve the Float <:< AnyVal is true property is to use a ClassTag as an extractor:

scala> def arrayConformsTo[A](as: Array[_])(implicit arrayOfA: ClassTag[Array[A]]) 
     | = as match {
     |     case arrayOfA(_) => true
     |     case _           => false
     |   }
like image 170
Régis Jean-Gilles Avatar answered Oct 27 '22 17:10

Régis Jean-Gilles


This works for me w/o any compiler warnings:

def arrayConformsTo[A](as: Array[_])(implicit t:ClassTag[A]) = {
  ClassTag(as.getClass().getComponentType()) equals t
}

Then this prints true and then false

println(arrayConformsTo[Int](Array(1,2,3)))
println(arrayConformsTo[String](Array(1,2,3)))
like image 33
cmbaxter Avatar answered Oct 27 '22 16:10

cmbaxter