Scala Reflection - Mirrors,ClassTag,TypeTag and WeakTypeTag

    反射reflection是程序对自身的检查、验证甚至代码修改功能。反射可以通过它的Reify功能来实时自动构建生成静态的Scala实例如:类(class)、方法(method)、表达式(expression)等。或者动态跟踪当前程序运算事件如:方法运算(method invocation)、字段引用(field access)等。反射又分编译时段与运算时段反射即:compile-time-reflection及runtime-reflection。我们使用compile-time-reflection在编译程序时指导编译器修改编译中代码或者产生新的代码,用runtime-reflection来进行实例的类型匹配、验证等。在v2.10之前,Scala没有自备的Reflection工具库,只能用Java Reflection库提供的部分功能来动态检验类型(class)或对象(object)及使用它们的字段(member access)。但java-reflection无法提供对某些scala项目的支持如:function、trait以及特殊类型如:existential、high-kinder、path-dependent、abstract types。特别是java-reflection无法获取泛类型在runtime过程中的信息,这个一直是一个诟病。直到scala2.10增加了新的reflection库才从根本上解决了针对scala特性的反射(refective)功能问题。scala-reflection同样提供了compile-time-reflection和runtime-reflection。其中compile-time-reflection是通过独立的macro库实现的。在这篇讨论里我们主要介绍runtime-reflection功能。

scala runtime-reflection有以下几项主要功能:

1、动态检验对象类型,包括泛类型

2、实时构建类型实例

3、实时调用类型的运算方法

反射功能可以在两种环境下体现:compile-time及runtime,是通过反射库的universe命名空间分辨的,即:

runtime-reflection     : scala.reflect.runtime.universe

compile-time-reflection: scala.reflect.macros.universe

我们必须import相应的命名空间来获取compile-time或runtime反射功能。

各种具体的runtime反射功能是通过Mirror来获取的,以runtimeMirror(...)为入口。下面是各种Mirror的获取和使用方法示范:

 1  val ru = scala.reflect.runtime.universe
 2   //runtime reflection入口
 3   val m = ru.runtimeMirror(getClass.getClassLoader)  //m: ru.Mirror = JavaMirror with java.net.URLClassLoader...
 4   //sample class
 5   class Person(name: String, age: Int) {
 6     var hight: Double = 0.0
 7     def getName = name
 8   }
 9   val john = new Person("John", 23) {
10     hight = 1.7
11   }
12   //instance mirror
13   val im = m.reflect(john)
14   //im: ru.InstanceMirror = instance mirror for...
15   //query method on instance
16   val mgetName = ru.typeOf[Person].decl(ru.TermName("getName")).asMethod
17   //mgetName: ru.MethodSymbol = method getName
18   //get method
19   val invoke_getName = im.reflectMethod(mgetName) //invoke_getName: ru.MethodMirror = ...
20   invoke_getName()
21   //res0: Any = John
22   //query field on instance
23   val fldHight = ru.typeOf[Person].decl(ru.TermName("hight")).asTerm
24   //fldHight: ru.TermSymbol = variable hight
25   //get field
26   val fmHight = im.reflectField(fldHight) //fmHight: ru.FieldMirror = ...
27   fmHight.get //res1: Any = 1.7
28   fmHight.set(1.6)
29   fmHight.get
30   //res3: Any = 1.6
31   val clsP = ru.typeOf[Person].typeSymbol.asClass
32   //get class mirror
33   val cm = m.reflectClass(clsP)
34   //get constructor symbol
35   val ctorP = ru.typeOf[Person].decl(ru.nme.CONSTRUCTOR).asMethod
36   //get contructor mirror
37   val ctorm = cm.reflectConstructor(ctorP)
38   val mary = ctorm("mary", 20).asInstanceOf[Person]
39   println(mary.getName)    // mary
40   object OB {
41     def x = 3
42   }
43   //get object symbol
44   val objOB = ru.typeOf[OB.type].termSymbol.asModule
45   //get module mirror
46   val mOB = m.reflectModule(objOB)
47   //get object instance
48   val instOB = mOB.instance.asInstanceOf[OB.type]
49   println(instOB.x)  // 3

上面例子里的typeOf[T]和typeTag[T].tpe及implicitly[TypeTag[T]].tpe是通用的,看下面的示范: 

1   val clsP = ru.typeTag[Person].tpe.typeSymbol.asClass  //ru.typeOf[Person].typeSymbol.asClass
2   val clsP1 = implicitly[ru.TypeTag[Person]].tpe.typeSymbol.asClass //clsP1: ru.ClassSymbol = class Person
3   val clsP2 = ru.typeTag[Person].tpe.typeSymbol.asClass //clsP2: ru.ClassSymbol = class Person

讲到TypeTag[T],这本是一个由compiler产生的结构,可以把在编译时段(compile-time)类型T的所有信息带到运算时段(runtime)。主要目的可能是为了解决JVM在编译过程中的类型擦拭(type erasure)问题:在运算过程中可以从TypeTag[T]中获取T类型信息(通过typeTag[T]),最终实现类型T的对比验证等操作:

1  def getType[T: ru.TypeTag](obj: T) = ru.typeTag[T].tpe
2   //> getType: [T](obj: T)(implicit evidence$1: ru.TypeTag[T]) ru.Type
3  def getType2[T: ru.TypeTag](obj: T) = ru.typeOf[T]
4   //> getType2: [T](obj: T)(implicit evidence$2: ru.TypeTag[T]) ru.Type
5  getType(List(1,2)) =:= getType2(List(3,4))       //> res0: Boolean = true
6  getType(List(1,2)) =:= getType2(List(3.0,4.0))   //> res1: Boolean = false
7  getType(List(1,2)) =:= ru.typeOf[List[Int]]      //> res2: Boolean = true

以上是通过隐式参数(implicit parameters)或者上下文界线(context bound)来指示compiler产生TypeTag[T]结构的。

我们可能经常碰到TypeTag的调用例子,还有WeakTypeTag和ClassTag。ClassTag应该是有明显区别的,因为它在另外一个命名空间里:

1  import scala.reflect.ClassTag
2  def extract[T: ClassTag](list: List[Any]) =  list.flatMap {
3    case elem: T => Some(elem)
4    case _ => None
5  }    //> extract: [T](list: List[Any])(implicit evidence$3: scala.reflect.ClassTag[T] )List[T]
6  extract[String](List(1,"One",2,3,"Four",List(5)))//> res4: List[String] = List(One, Four)

ClassTag在scala.reflect.ClassTag里。这个extract函数的目的是把T类型的值过滤出来。上面的例子里list里的String元素被筛选出来了。但是如果我们像下面这样使用extract呢?

1 extract[List[Int]](List(List(1,2),List("a","b")))//> res5: List[List[Int]] = List(List(1, 2), List(a, b))

这次extract得出错误的运算结果,因为我们指明的是过滤List[Int]。这是因为ClassTag不支持高阶类型,List[Int]就是个高阶类型。那么extract[String]是怎样正确工作的呢?是因为compiler对模式匹配进行了这样的转换处理:

case elem: T >>> case elem @tag(_:T)

通过ClassTag[T]隐式实例(implicit instance)可以正确推导出elem的类型。在上面的例子里我们通过ClassTag得出T就是String。分析得出ClassTag可以分辨基础类型但无法分辨像List[Int],List[String]这样的高阶类型。

TypeTag包含了完整的类型信息可以分辨List[Int],List[String],甚至List[Set[Int]],List[Set[String]]这样的高阶类型。也就是TypeTag结构内包含了高阶类型内包嵌的类型,只有如此才能解决类型擦拭(type erasure)问题。我们用下面的例子来示范TypeTag的内容:

 1  def getInnerType[T: ru.TypeTag](obj: T) = ru.typeTag[T].tpe match {
 2    case ru.TypeRef(utype,usymb,args) =>
 3      List(utype,usymb,args).mkString("\n")
 4  }      //> getInnerType: [T](obj: T)(implicit evidence$4: worksheets.reflect.ru.TypeTag[T])String
 5  getInnerType(List(1,2))                          //> res6: String = scala.collection.immutable.type
 6                                                   //| class List
 7                                                   //| List(Int)
 8  getInnerType(List(List(1,2)))                    //> res7: String = scala.collection.immutable.type
 9                                                   //| class List
10                                                   //| List(List[Int])
11  getInnerType(Set(List(1,2)))                     //> res8: String = scala.collection.immutable.type
12                                                   //| trait Set
13                                                   //| List(List[Int])
14  getInnerType(List(Set("a","b")))                 //> res9: String = scala.collection.immutable.type
15                                                   //| class List
16                                                   //| List(scala.collection.immutable.Set[java.lang.String])

上面的例子里getInnerType可以分辨高阶类型内的类型,args就是承载这个内部类型的List。那么如果我们为extract函数提供一个TypeTag又如何呢?看看下面的示范:

1  def extract[T: ru.TypeTag](list: List[Any]) =  list.flatMap {
2    case elem: T => Some(elem)
3    case _ => None
4  }    //> extract: [T](list: List[Any])(implicit evidence$3: ru.TypeTag[T])List[T]
5  extract[String](List(1,"One",2,3,"Four",List(5)))//> res4: List[String] = List(1, One, 2, 3, Four, List(5))
6  extract[List[Int]](List(List(1,2),List("a","b")))//> res5: List[List[Int]] = List(List(1, 2), List(a, b))

可以看到,虽然compiler产生并提供了TypeTag隐式参数evidence$3,但运算结果并不正确,这是为什么呢?从这个例子可以证实了ClassTag和TypeTag最大的区别:ClassTag在运算时提供了一个实例的类型信息,而TypeTag在运算时提供了一个类型的完整信息。我们只能用ClassTag来比较某个值的类型,而在运算时用TypeTag只能进行类型对比。extract中elem是List里的一个元素,是个值,所以只能用ClassTag来判别这个值的类型。如果使用TypeTag的话我们只能实现像下面示例中的类型对比:

 1  def meth[T: ru.TypeTag](xs: List[T]) = ru.typeTag[T].tpe match {
 2     case t if t =:= ru.typeOf[Int] => "list of integer"
 3     case t if t =:= ru.typeOf[List[String]] => "list of list of string"
 4     case t if t =:= ru.typeOf[Set[List[Int]]] => "list of set of list of integer"
 5     case _ => "some other types"
 6  }     //> meth: [T](xs: List[T])(implicit evidence$5: ru.TypeTag[T])String
 7  meth(List(1,2,3))                                //> res10: String = list of integer
 8  meth(List("a","b"))                              //> res11: String = some other types
 9  meth(List(List("a","a")))                        //> res12: String = list of list of string
10  meth(List(Set(List(1,20))))                      //> res13: String = list of set of list of integer

我们只能在运算时对T进行类型匹配。总结以上分析,ClassTag与TypeTag有下面几点重要区别:

1、ClassTag不适用于高阶类型:对于List[T],ClassTag只能分辨是个List,但无法获知T的类型。所以ClassTag不能用来解决类型擦拭(type erasure)问题

2、TypeTag通过完整的类型信息可以分辨高阶类型的内部类型,但它无法提供运算时(runtime)某个实例的类型。总的来说:TypeTag提供了runtime的类型信息,ClassTag提供runtime实例信息(所以ClassTag就像typeclass,能提供很多类型的隐型实例)

那么这个WeakTypeTag又是用来干什么的?它与TypeTag又有什么分别呢?如果我们把上面的meth函数改成使用WeakTypeTag:

 1  def meth[T: ru.WeakTypeTag](xs: List[T]) = ru.weakTypeTag[T].tpe match {
 2     case t if t =:= ru.typeOf[Int] => "list of integer"
 3     case t if t =:= ru.typeOf[List[String]] => "list of list of string"
 4     case t if t =:= ru.typeOf[Set[List[Int]]] => "list of set of list of integer"
 5     case _ => "some other types"
 6  }      //> meth: [T](xs: List[T])(implicit evidence$5: ru.WeakTypeTag[T])String
 7  meth(List(1,2,3))                                //> res10: String = list of integer
 8  meth(List("a","b"))                              //> res11: String = some other types
 9  meth(List(List("a","a")))                        //> res12: String = list of list of string
10  meth(List(Set(List(1,20))))                      //> res13: String = list of set of list of integer

结果与使用TypeTag一致,好像WeakTypeTag和TypeTag没什么分别。从字面解释,WeakTypeTag应该在某些方面要求比较松。在上面的例子里调用meth函数时我们提供了一个实质类型如:List[Int],List[String],List[List[Int]]等。如果我们只能提供像List[T]这样的抽象类型的话,compiler一定会吵闹,像下面的示范:

1 // def foo[T] = ru.typeTag[T]         //> No TypeTag available for T
2  def foo[T] = ru.weakTypeTag[T]       //> foo: [T]=> worksheets.reflect.ru.WeakTypeTag[T]

看来WeakTypeTag可以支持抽象类型。我们再看看下面的示范:

 1 abstract class SomeClass[T] {
 2  def getInnerWeakType[T: ru.WeakTypeTag](obj: T) = ru.weakTypeTag[T].tpe match {
 3    case ru.TypeRef(utype,usymb,args) =>
 4      List(utype,usymb,args).mkString("\n")
 5  }
 6  val list: List[T]
 7  val result = getInnerWeakType(list)
 8 }
 9 val sc = new SomeClass[Int] { val list = List(1,2,3) }
10                                                   //> sc  : SomeClass[Int] 
11 println(sc.result)                                //> scala.type
12                                                   //| type List
13                                                   //| List(T)

首先我们可以得出List[T]的内部类型就是T。更重要的是如果不使用WeakTypeTag的话getInnerWeakType(list)根本无法通过编译。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编舟记

泛型编程

泛型编程是一种编程风格,其中算法以尽可能抽象的方式编写,而不依赖于将在其上执行这些算法的数据形式。

592
来自专栏一个会写诗的程序员的博客

第5章 函数与函数式编程第5章 函数与函数式编程

函数式编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以传入函数参数,也可以返回一个函数。函数式编程 (简称FP) 是一种编程...

531
来自专栏一个会写诗的程序员的博客

Java8 Lambda表达式.md什么是λ表达式λ表达式的类型λ表达式的使用其它相关概念

为了支持函数式编程,Java 8引入了Lambda表达式. 在Java 8中采用的是内部类来实现Lambda表达式.具体实现代码,可以通过debug看, 同时...

793
来自专栏浪淘沙

Scala学习笔记

大数据框架(处理海量数据/处理实时流式数据) 一:以hadoop2.X为体系的海量数据处理框架         离线数据分析,往往分析的是N+1的数据  ...

1764
来自专栏风口上的猪的文章

.NET面试题系列[8] - 泛型

“可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用。“ - Jon Skeet

1063
来自专栏我和未来有约会

(保存)C#基础概念二十五问

注:本文部份资料来自网络,如有侵权,请与我联系,我会在第一时间声明引用或将其删除!     当初学 C# 时是找个人大概问了一下数据类型和分支语句就开始做项目了...

1858
来自专栏风口上的猪的文章

.NET面试题系列[6] - 反射

在面试中,通常会考察反射的定义(操作元数据),可以用反射做什么(获得程序集及其各个部件),反射有什么使用场景(ORM,序列化,反序列化,值类型比较等)。如果答得...

722
来自专栏Jimoer

java设计模式之代理模式

代理模式 代理模式是常见设计模式的一种,代理模式的定义是:为其他对象提供一种代理以控制对这个对象的访问。 在某些情况下,一个对象不适合或者不能直接引用另一个对象...

3055
来自专栏Play & Scala 技术分享

Scala 谜题 - 有趣的类型转换

3217
来自专栏python成长之路

self,和类实例化加不加括号的理解

1685

扫码关注云+社区