21

我有一个函数,它能够知道一个对象是否是 aManifest类型的实例。我想将它迁移到一个TypeTag版本。旧功能如下:

def myIsInstanceOf[T: Manifest](that: Any) = 
  implicitly[Manifest[T]].erasure.isInstance(that)

我一直在尝试使用 TypeTag,现在我有了这个 TypeTag 版本:

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

有没有更好的方法来完成这项任务?U是否可以使用 an 来省略参数化Any(就像在旧函数中所做的那样)?

4

4 回答 4

22

如果对擦除类型使用子类型检查就足够了,请按照 Travis Brown 在上面的评论中建议的那样做:

def myIsInstanceOf[T: ClassTag](that: Any) =
  classTag[T].runtimeClass.isInstance(that)

否则,您需要明确说明U类型,以便 scalac 在类型标记中捕获其类型:

def myIsInstanceOf[T: TypeTag, U: TypeTag] =
  typeOf[U] <:< typeOf[T]
于 2012-07-24T10:54:59.073 回答
10

在您的特定情况下,如果您确实需要迁移现有代码并保持相同的行为,则需要ClassTag. 使用TypeTag更精确,但正是因为某些代码的行为会有所不同,所以(通常)你需要小心。

如果你确实想要TypeTag,我们可以比上面的语法做得更好;在调用处的效果与省略 相同U

推荐的替代品

使用拉皮条

对于 Eugene 的回答,必须拼写两种类型,而最好推断that. 给定一个类型参数列表,要么指定全部,要么不指定;类型柯里化可能会有所帮助,但只是拉皮条似乎更简单。让我们使用这个在 2.10 中也新增的隐式类,仅用 3 行来定义我们的解决方案。

import scala.reflect.runtime.universe._
implicit class MyInstanceOf[U: TypeTag](that: U) {
  def myIsInstanceOf[T: TypeTag] = 
    typeOf[U] <:< typeOf[T]
}

事实上,我会争辩说,像这样的东西,用一个更好的名字(比如stat_isInstanceOf),甚至可以属于 Predef。

使用示例:

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

更兼容的语法

如果拉皮条是一个问题,因为它改变了调用语法并且你有现有的代码,我们可以尝试如下类型的柯里化(使用起来更棘手),这样只需要显式传递一个类型参数 - 就像在你的旧定义中一样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

如果您想自己学习编写此类代码,您可能会对下面显示的变体讨论感兴趣。

其他变体和失败的尝试

使用结构类型可以使上述定义更加紧凑:

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}

然而,使用结构类型并不总是一个好主意,正如 -feature 警告的那样:

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

问题是由于反射导致的减速,这是实现结构类型所必需的。修复它很容易,只是使代码更长一点,如上所示。

我必须避免的一个陷阱

在上面的代码中,我第一次尝试写[T]而不是。[T: TypeTag]有趣的是为什么它会失败。要理解这一点,请看一下:

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}

如果您仔细查看返回值的类型,您会发现它是implicit TypeTag[T] => U => implicit TypeTag[U](在伪 Scala 表示法中)。当你传递一个参数时,Scala 会认为它是第一个参数列表,隐式的:

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

一个提示

最后也是最不重要的一个提示,您可能感兴趣也可能不感兴趣:在此尝试中,您将两次传递 TypeTag[T] - 因此您应该删除: TypeTagafter [T

def myIsInstanceOf[T: TypeTag, U: TypeTag](tag: TypeTag[T], that: U) = 
  myInstanceToTpe(that) stat_<:< tag.tpe
于 2012-07-26T17:08:55.420 回答
0

我使用上述建议提出以下建议。欢迎反馈。

/*
    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
    }
于 2013-12-18T19:16:02.593 回答
0

您还可以从 TypeTag 捕获类型(到类型别名中),但前提是它没有被擦除,因此它在函数内部不起作用:

如何从 TypeTag[T] 或 scala 中的任何其他泛型中捕获 T?

于 2014-09-26T07:42:00.503 回答