【问题标题】:How to know if an object is an instance of a TypeTag's type?如何知道一个对象是否是 TypeTag 类型的实例?
【发布时间】:2012-07-24 09:54:55
【问题描述】:

我有一个函数可以知道一个对象是否是Manifest 类型的实例。我想将它迁移到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,而改用Any(就像在旧函数中所做的那样)?

【问题讨论】:

  • 不完全是您问题的答案,但您可以在(非弃用)2.10 中编写完全相同的内容,方法是将 Manifest 替换为 ClassTagerasure 替换为 runtimeClass
  • 我刚刚意识到TypeTag 并不总是比ClassTag 更精确(因为它不受擦除限制),它也不太精确:当T 是不是静态的超类型时that 的类型,但只有其动态类型,myIsInstanceOf 将失败。这适用于使用TypeTag 的所有建议。要诊断至少某些情况,需要删除TypeTags 并检查!(typeOf[U].erased &lt;:&lt; typeOf[T].erased) &amp;&amp; classTag[T].runtimeClass isInstance that

标签: scala instanceof scala-2.10


【解决方案1】:

如果对已擦除类型使用子类型检查就足够了,请按照 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]

【讨论】:

  • 谢谢,我会尝试在下面发布一个派生的替代方案。
  • 我收到此错误:对象 ClassTag 中应用的方法缺少参数;如果要将其视为部分应用的函数,请在此方法后面加上 `_' [ERROR] ClassTag[T].runtimeClass.isInstanceOf[Map]=>
【解决方案2】:

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

如果你确实想要TypeTag,我们可以比上面的语法做得更好;在调用点的效果和省略U一样。

推荐的替代品

使用拉皮条

对于 Eugene 的回答,必须拼写两种类型,而最好推断出 that 的类型。给定一个类型参数列表,要么指定全部,要么不指定; type currying 可能会有所帮助,但只是拉皮条似乎更简单。让我们使用这个在 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

更兼容的语法

如果 pimping 是一个问题,因为它改变了调用语法并且你有现有的代码,我们可以尝试如下类型的柯里化(使用起来更棘手),这样只需显式传递一个类型参数 - 就像你的旧用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] =&gt; U =&gt; 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] - 因此您应该在 [T 之后删除 : TypeTag

def myIsInstanceOf[T: TypeTag, U: TypeTag](tag: TypeTag[T], that: U) = 
  myInstanceToTpe(that) stat_<:< tag.tpe

【讨论】:

    【解决方案3】:

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

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

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

      How to capture T from TypeTag[T] or any other generic in scala?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-04-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多