问题
I have a function which is able to know if an object is an instance of a Manifest
's type. I would like to migrate it to a TypeTag
version. The old function is the following one:
def myIsInstanceOf[T: Manifest](that: Any) =
implicitly[Manifest[T]].erasure.isInstance(that)
I have been experimenting with the TypeTags and now I have this TypeTag version:
// Involved definitions
def myInstanceToTpe[T: TypeTag](x: T) = typeOf[T]
def myIsInstanceOf[T: TypeTag, U: TypeTag](tag: TypeTag[T], that: U) =
myInstanceToTpe(that) stat_<:< tag.tpe
// Some invocation examples
class A
class B extends A
class C
myIsInstanceOf(typeTag[A], new A) /* true */
myIsInstanceOf(typeTag[A], new B) /* true */
myIsInstanceOf(typeTag[A], new C) /* false */
Is there any better way to achieve this task? Can the parameterized U
be omitted, using an Any
instead (just as it is done in the old function)?
回答1:
If it suffices to use subtyping checks on erased types, do as Travis Brown suggested in the comment above:
def myIsInstanceOf[T: ClassTag](that: Any) =
classTag[T].runtimeClass.isInstance(that)
Otherwise you need to explicitly spell out the U
type, so that scalac captures its type in a type tag:
def myIsInstanceOf[T: TypeTag, U: TypeTag] =
typeOf[U] <:< typeOf[T]
回答2:
In your specific case, if you actually need to migrate existing code and keep the same behavior, you want ClassTag
. Using TypeTag
is more precise, but exactly because of that some code is going to behave differently, so (in general) you need to be careful.
If you indeed want TypeTag
, we can do even better than the above syntax; the effect at the call site is the same as omitting U
.
Recommended alternatives
Using pimping
With Eugene's answer, one has to spell both types, while it's desirable to deduce the type of that
. Given a type parameter list, either all or none are specified; type currying could maybe help, but it seems simpler to just pimp the method. Let's use for this implicit classes, also new in 2.10, to define our solution in just 3 lines.
import scala.reflect.runtime.universe._
implicit class MyInstanceOf[U: TypeTag](that: U) {
def myIsInstanceOf[T: TypeTag] =
typeOf[U] <:< typeOf[T]
}
I would in fact argue that something like this, with a better name (say stat_isInstanceOf
), could even belong into Predef.
Use examples:
//Support testing (copied from above)
class A
class B extends A
class C
//Examples
(new B).myIsInstanceOf[A] //true
(new B).myIsInstanceOf[C] //false
//Examples which could not work with erasure/isInstanceOf/classTag.
List(new B).myIsInstanceOf[List[A]] //true
List(new B).myIsInstanceOf[List[C]] //false
//Set is invariant:
Set(new B).myIsInstanceOf[Set[A]] //false
Set(new B).myIsInstanceOf[Set[B]] //true
//Function1[T, U] is contravariant in T:
((a: B) => 0).myIsInstanceOf[A => Int] //false
((a: A) => 0).myIsInstanceOf[A => Int] //true
((a: A) => 0).myIsInstanceOf[B => Int] //true
A more compatible syntax
If pimping is a problem because it changes the invocation syntax and you have existing code, we can try type currying (more tricky to use) as follows, so that just one type parameter has to be passed explicitly - as in your old definition with Any
:
trait InstanceOfFun[T] {
def apply[U: TypeTag](that: U)(implicit t: TypeTag[T]): Boolean
}
def myIsInstanceOf[T] = new InstanceOfFun[T] {
def apply[U: TypeTag](that: U)(implicit t: TypeTag[T]) =
typeOf[U] <:< typeOf[T]
}
myIsInstanceOf[List[A]](List(new B)) //true
If you want to learn to write such code yourself, you might be interested in the discussion of variations shown below.
Other variations and failed attempts
The above definition can be made more compact with structural types:
scala> def myIsInstanceOf[T] = new { //[T: TypeTag] does not give the expected invocation syntax
def apply[U: TypeTag](that: U)(implicit t: TypeTag[T]) =
typeOf[U] <:< typeOf[T]
}
myIsInstanceOf: [T]=> Object{def apply[U](that: U)(implicit evidence$1: reflect.runtime.universe.TypeTag[U],implicit t: reflect.runtime.universe.TypeTag[T]): Boolean}
Using structural types is however not always a good idea, as -feature warns:
scala> myIsInstanceOf[List[A]](List(new B))
<console>:14: warning: reflective access of structural type member method apply should be enabled
by making the implicit value language.reflectiveCalls visible.
This can be achieved by adding the import clause 'import language.reflectiveCalls'
or by setting the compiler option -language:reflectiveCalls.
See the Scala docs for value scala.language.reflectiveCalls for a discussion
why the feature should be explicitly enabled.
myIsInstanceOf[List[A]](List(new B))
^
res3: Boolean = true
The problem is the slowdown due to reflection, required to implement structural types. Fixing it is easy, just makes the code a bit longer, as seen above.
A pitfall I had to avoid
In the above code, I write [T]
instead of [T: TypeTag]
, my first attempt. It is interesting why it fails. To understand that, take a look:
scala> def myIsInstanceOf[T: TypeTag] = new {
| def apply[U: TypeTag](that: U) =
| typeOf[U] <:< typeOf[T]
| }
myIsInstanceOf: [T](implicit evidence$1: reflect.runtime.universe.TypeTag[T])Object{def apply[U](that: U)(implicit evidence$2: reflect.runtime.universe.TypeTag[U]): Boolean}
If you look carefully at the type of the return value, you can see it's implicit TypeTag[T] => U => implicit TypeTag[U]
(in pseudo-Scala notation). When you pass an argument, Scala will think it's for the first parameter list, the implicit one:
scala> myIsInstanceOf[List[A]](List(new B))
<console>:19: error: type mismatch;
found : List[B]
required: reflect.runtime.universe.TypeTag[List[A]]
myIsInstanceOf[List[A]](List(new B))
^
A tip
Last and least, one tip which might or not interest you: in this attempt, you are passing TypeTag[T] twice - hence you should remove : TypeTag
after [T
.
def myIsInstanceOf[T: TypeTag, U: TypeTag](tag: TypeTag[T], that: U) =
myInstanceToTpe(that) stat_<:< tag.tpe
回答3:
I used the above suggestions to come up with the following. Feedback is welcomed.
/*
Attempting to cast Any to a Type of T, using TypeTag
http://stackoverflow.com/questions/11628379/how-to-know-if-an-object-is-an-instance-of-a-typetags-type
*/
protected def toOptInstance[T: ClassTag](any: Any) =
classTag[T].runtimeClass.isInstance(any) match {
case true =>
Try(any.asInstanceOf[T]).toOption
case false =>
/*
Allow only primitive casting
*/
if (classTag[T].runtimeClass.isPrimitive)
any match {
case u: Unit =>
castIfCaonical[T](u, "void")
case z: Boolean =>
castIfCaonical[T](z, "boolean")
case b: Byte =>
castIfCaonical[T](b, "byte")
case c: Char =>
castIfCaonical[T](c, "char")
case s: Short =>
castIfCaonical[T](s, "short")
case i: Int =>
castIfCaonical[T](i, "int")
case j: Long =>
castIfCaonical[T](j, "long")
case f: Float =>
castIfCaonical[T](f, "float")
case d: Double =>
castIfCaonical[T](d, "double")
case _ =>
None
}
else None
}
protected def castIfCaonical[T: ClassTag](value: AnyVal, canonicalName: String): Option[T] ={
val trueName = classTag[T].runtimeClass.getCanonicalName
if ( trueName == canonicalName)
Try(value.asInstanceOf[T]).toOption
else None
}
回答4:
You can also capture type from TypeTag (into type alias), but only if it's not erased, so it will not work inside function:
How to capture T from TypeTag[T] or any other generic in scala?
来源:https://stackoverflow.com/questions/11628379/how-to-know-if-an-object-is-an-instance-of-a-typetags-type