How to know if an object is an instance of a TypeTag's type?

六月ゝ 毕业季﹏ 提交于 2019-11-27 02:36:34

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!