在Scala 2.11+反射中,如何可靠地将TypeTag和Manifest相互转换? [英] In Scala 2.11+ reflection, how to reliably convert a TypeTag and a Manifest into each other?

查看:66
本文介绍了在Scala 2.11+反射中,如何可靠地将TypeTag和Manifest相互转换?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这篇文章中:

可以将TypeTag转换为清单?

表明可以使用以下代码将TypeTag转换为清单:

  def toManifest[T:TypeTag]: Manifest[T] = {
    val t = typeTag[T]
    val mirror = t.mirror
    def toManifestRec(t: Type): Manifest[_] = {
      val clazz = ClassTag[T](mirror.runtimeClass(t)).runtimeClass
      if (t.typeArgs.length == 1) {
        val arg = toManifestRec(t.typeArgs.head)
        ManifestFactory.classType(clazz, arg)
      } else if (t.typeArgs.length > 1) {
        val args = t.typeArgs.map(x => toManifestRec(x))
        ManifestFactory.classType(clazz, args.head, args.tail: _*)
      } else {
        ManifestFactory.classType(clazz)
      }
    }
    toManifestRec(t.tpe).asInstanceOf[Manifest[T]]
  }

它不起作用,如以下测试用例所示:

object TypeTag2Manifest {

  class Example {

    type T = Map[String, Int]
  }
  val example = new Example

}

class TypeTag2Manifest extends FunSpec {

  import org.apache.spark.sql.catalyst.ScalaReflection.universe._
  import TypeTag2Manifest._

  it("can convert") {

    val t1 = implicitly[TypeTag[example.T]]
    val v1 = toManifest(t1)
    val v2 = implicitly[Manifest[example.T]]

    assert(v1 == v2)
  }
}

输出:

scala.collection.immutable.Map did not equal scala.collection.immutable.Map[java.lang.String, Int]
ScalaTestFailureLocation: com.tribbloids.spike.scala_spike.reflection.TypeTag2Manifest at (TypeTag2Manifest.scala:52)
Expected :scala.collection.immutable.Map[java.lang.String, Int]
Actual   :scala.collection.immutable.Map

很明显,这表明类型擦除一直困扰着转换,尽管TypeTag旨在避免类型擦除,但只能解析为从属类型example.T,而不能从基础类型Map[String, Int]获取正确的类型参数./p>

那么将TypeTag和Manifest彼此转换为不吮吸的方式是什么?

解决方案

如果替换

toManifestRec(t.tpe).asInstanceOf[Manifest[T]]

toManifestRec(t.tpe.dealias).asInstanceOf[Manifest[T]]

这可以改善toManifest,但不能完全改善:

scala.collection.immutable.Map[java.lang.String, int]

vs.

scala.collection.immutable.Map[java.lang.String, Int]

即Java原语intscala.Int.

问题出在

中的第二个.runtimeClass

val clazz = ClassTag[T](mirror.runtimeClass(t)).runtimeClass 

mirror.runtimeClass(t)intClassTag[T](mirror.runtimeClass(t))Int,但ClassTag[T](mirror.runtimeClass(t)).runtimeClass再次是int.

所以请尝试进一步改善toManifest

def toManifest[T: TypeTag]: Manifest[T] = {
  val t = typeTag[T]
  val mirror = t.mirror
  def toManifestRec(t: Type): Manifest[_] = {
    ClassTag[T](mirror.runtimeClass(t)) match {
      case ClassTag.Byte    => Manifest.Byte
      case ClassTag.Short   => Manifest.Short
      case ClassTag.Char    => Manifest.Char
      case ClassTag.Int     => Manifest.Int
      case ClassTag.Long    => Manifest.Long
      case ClassTag.Float   => Manifest.Float
      case ClassTag.Double  => Manifest.Double
      case ClassTag.Boolean => Manifest.Boolean
      case ClassTag.Unit    => Manifest.Unit
      case ClassTag.Object  => Manifest.Object
      case ClassTag.Nothing => Manifest.Nothing
      case ClassTag.Null    => Manifest.Null
      case classTag         =>
        val clazz = classTag.runtimeClass
        if (t.typeArgs.length >= 1) {
          val args = t.typeArgs.map(x => toManifestRec(x))
          ManifestFactory.classType(clazz, args.head, args.tail: _*)
        } else {
          ManifestFactory.classType(clazz)
        }
    }
  }
  toManifestRec(t.tpe.dealias).asInstanceOf[Manifest[T]]
}

val t1 = implicitly[TypeTag[example.T]] //TypeTag[TypeTag2Manifest.example.T]
val v1 = toManifest(t1) //scala.collection.immutable.Map[java.lang.String, Int]
val v2 = implicitly[Manifest[example.T]] //scala.collection.immutable.Map[java.lang.String, Int]
v1 == v2 //true

In this post:

Is it possible to convert a TypeTag to a Manifest?

It is indicated that a TypeTag can be converted into a Manifest using the following code:

  def toManifest[T:TypeTag]: Manifest[T] = {
    val t = typeTag[T]
    val mirror = t.mirror
    def toManifestRec(t: Type): Manifest[_] = {
      val clazz = ClassTag[T](mirror.runtimeClass(t)).runtimeClass
      if (t.typeArgs.length == 1) {
        val arg = toManifestRec(t.typeArgs.head)
        ManifestFactory.classType(clazz, arg)
      } else if (t.typeArgs.length > 1) {
        val args = t.typeArgs.map(x => toManifestRec(x))
        ManifestFactory.classType(clazz, args.head, args.tail: _*)
      } else {
        ManifestFactory.classType(clazz)
      }
    }
    toManifestRec(t.tpe).asInstanceOf[Manifest[T]]
  }

It doesn't work, as demonstrated in the following test case:

object TypeTag2Manifest {

  class Example {

    type T = Map[String, Int]
  }
  val example = new Example

}

class TypeTag2Manifest extends FunSpec {

  import org.apache.spark.sql.catalyst.ScalaReflection.universe._
  import TypeTag2Manifest._

  it("can convert") {

    val t1 = implicitly[TypeTag[example.T]]
    val v1 = toManifest(t1)
    val v2 = implicitly[Manifest[example.T]]

    assert(v1 == v2)
  }
}

Output:

scala.collection.immutable.Map did not equal scala.collection.immutable.Map[java.lang.String, Int]
ScalaTestFailureLocation: com.tribbloids.spike.scala_spike.reflection.TypeTag2Manifest at (TypeTag2Manifest.scala:52)
Expected :scala.collection.immutable.Map[java.lang.String, Int]
Actual   :scala.collection.immutable.Map

Evidently this indicates that type erasure has been plaguing the conversion, and TypeTag, despite being designed to avoid type erasure, can only resolve to the dependent type example.T without getting the correct type arguments from underlying type Map[String, Int].

So what is the way to convert TypeTag and Manifest into each other that doesn't suck?

解决方案

If you replace

toManifestRec(t.tpe).asInstanceOf[Manifest[T]]

with

toManifestRec(t.tpe.dealias).asInstanceOf[Manifest[T]]

this improves toManifest but not completely:

scala.collection.immutable.Map[java.lang.String, int]

vs.

scala.collection.immutable.Map[java.lang.String, Int]

i.e. Java primitive int vs. scala.Int.

The problem is in the second .runtimeClass in

val clazz = ClassTag[T](mirror.runtimeClass(t)).runtimeClass 

mirror.runtimeClass(t) is int, ClassTag[T](mirror.runtimeClass(t)) is Int but ClassTag[T](mirror.runtimeClass(t)).runtimeClass is again int.

So try to improve toManifest more

def toManifest[T: TypeTag]: Manifest[T] = {
  val t = typeTag[T]
  val mirror = t.mirror
  def toManifestRec(t: Type): Manifest[_] = {
    ClassTag[T](mirror.runtimeClass(t)) match {
      case ClassTag.Byte    => Manifest.Byte
      case ClassTag.Short   => Manifest.Short
      case ClassTag.Char    => Manifest.Char
      case ClassTag.Int     => Manifest.Int
      case ClassTag.Long    => Manifest.Long
      case ClassTag.Float   => Manifest.Float
      case ClassTag.Double  => Manifest.Double
      case ClassTag.Boolean => Manifest.Boolean
      case ClassTag.Unit    => Manifest.Unit
      case ClassTag.Object  => Manifest.Object
      case ClassTag.Nothing => Manifest.Nothing
      case ClassTag.Null    => Manifest.Null
      case classTag         =>
        val clazz = classTag.runtimeClass
        if (t.typeArgs.length >= 1) {
          val args = t.typeArgs.map(x => toManifestRec(x))
          ManifestFactory.classType(clazz, args.head, args.tail: _*)
        } else {
          ManifestFactory.classType(clazz)
        }
    }
  }
  toManifestRec(t.tpe.dealias).asInstanceOf[Manifest[T]]
}

val t1 = implicitly[TypeTag[example.T]] //TypeTag[TypeTag2Manifest.example.T]
val v1 = toManifest(t1) //scala.collection.immutable.Map[java.lang.String, Int]
val v2 = implicitly[Manifest[example.T]] //scala.collection.immutable.Map[java.lang.String, Int]
v1 == v2 //true

这篇关于在Scala 2.11+反射中,如何可靠地将TypeTag和Manifest相互转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆