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的获取和使用方法示范:
val ru = scala.reflect.runtime.universe
//runtime reflection入口
val m = ru.runtimeMirror(getClass.getClassLoader) //m: ru.Mirror = JavaMirror with java.net.URLClassLoader...
//sample class
class Person(name: String, age: Int) {
var hight: Double = 0.0
def getName = name
}
val john = new Person("John", ) {
hight = 1.7
}
//instance mirror
val im = m.reflect(john)
//im: ru.InstanceMirror = instance mirror for...
//query method on instance
val mgetName = ru.typeOf[Person].decl(ru.TermName("getName")).asMethod
//mgetName: ru.MethodSymbol = method getName
//get method
val invoke_getName = im.reflectMethod(mgetName) //invoke_getName: ru.MethodMirror = ...
invoke_getName()
//res0: Any = John
//query field on instance
val fldHight = ru.typeOf[Person].decl(ru.TermName("hight")).asTerm
//fldHight: ru.TermSymbol = variable hight
//get field
val fmHight = im.reflectField(fldHight) //fmHight: ru.FieldMirror = ...
fmHight.get //res1: Any = 1.7
fmHight.set(1.6)
fmHight.get
//res3: Any = 1.6
val clsP = ru.typeOf[Person].typeSymbol.asClass
//get class mirror
val cm = m.reflectClass(clsP)
//get constructor symbol
val ctorP = ru.typeOf[Person].decl(ru.nme.CONSTRUCTOR).asMethod
//get contructor mirror
val ctorm = cm.reflectConstructor(ctorP)
val mary = ctorm("mary", ).asInstanceOf[Person]
println(mary.getName) // mary
object OB {
def x =
}
//get object symbol
val objOB = ru.typeOf[OB.type].termSymbol.asModule
//get module mirror
val mOB = m.reflectModule(objOB)
//get object instance
val instOB = mOB.instance.asInstanceOf[OB.type]
println(instOB.x) //
上面例子里的typeOf[T]和typeTag[T].tpe及implicitly[TypeTag[T]].tpe是通用的,看下面的示范:
val clsP = ru.typeTag[Person].tpe.typeSymbol.asClass //ru.typeOf[Person].typeSymbol.asClass
val clsP1 = implicitly[ru.TypeTag[Person]].tpe.typeSymbol.asClass //clsP1: ru.ClassSymbol = class Person
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的对比验证等操作:
def getType[T: ru.TypeTag](obj: T) = ru.typeTag[T].tpe
//> getType: [T](obj: T)(implicit evidence$1: ru.TypeTag[T]) ru.Type
def getType2[T: ru.TypeTag](obj: T) = ru.typeOf[T]
//> getType2: [T](obj: T)(implicit evidence$2: ru.TypeTag[T]) ru.Type
getType(List(,)) =:= getType2(List(,)) //> res0: Boolean = true
getType(List(,)) =:= getType2(List(3.0,4.0)) //> res1: Boolean = false
getType(List(,)) =:= ru.typeOf[List[Int]] //> res2: Boolean = true
以上是通过隐式参数(implicit parameters)或者上下文界线(context bound)来指示compiler产生TypeTag[T]结构的。
我们可能经常碰到TypeTag的调用例子,还有WeakTypeTag和ClassTag。ClassTag应该是有明显区别的,因为它在另外一个命名空间里:
import scala.reflect.ClassTag
def extract[T: ClassTag](list: List[Any]) = list.flatMap {
case elem: T => Some(elem)
case _ => None
} //> extract: [T](list: List[Any])(implicit evidence$3: scala.reflect.ClassTag[T] )List[T]
extract[String](List(,"One",,,"Four",List()))//> res4: List[String] = List(One, Four)
ClassTag在scala.reflect.ClassTag里。这个extract函数的目的是把T类型的值过滤出来。上面的例子里list里的String元素被筛选出来了。但是如果我们像下面这样使用extract呢?
extract[List[Int]](List(List(,),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的内容:
def getInnerType[T: ru.TypeTag](obj: T) = ru.typeTag[T].tpe match {
case ru.TypeRef(utype,usymb,args) =>
List(utype,usymb,args).mkString("\n")
} //> getInnerType: [T](obj: T)(implicit evidence$4: worksheets.reflect.ru.TypeTag[T])String
getInnerType(List(,)) //> res6: String = scala.collection.immutable.type
//| class List
//| List(Int)
getInnerType(List(List(,))) //> res7: String = scala.collection.immutable.type
//| class List
//| List(List[Int])
getInnerType(Set(List(,))) //> res8: String = scala.collection.immutable.type
//| trait Set
//| List(List[Int])
getInnerType(List(Set("a","b"))) //> res9: String = scala.collection.immutable.type
//| class List
//| List(scala.collection.immutable.Set[java.lang.String])
上面的例子里getInnerType可以分辨高阶类型内的类型,args就是承载这个内部类型的List。那么如果我们为extract函数提供一个TypeTag又如何呢?看看下面的示范:
def extract[T: ru.TypeTag](list: List[Any]) = list.flatMap {
case elem: T => Some(elem)
case _ => None
} //> extract: [T](list: List[Any])(implicit evidence$3: ru.TypeTag[T])List[T]
extract[String](List(,"One",,,"Four",List()))//> res4: List[String] = List(1, One, 2, 3, Four, List(5))
extract[List[Int]](List(List(,),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的话我们只能实现像下面示例中的类型对比:
def meth[T: ru.TypeTag](xs: List[T]) = ru.typeTag[T].tpe match {
case t if t =:= ru.typeOf[Int] => "list of integer"
case t if t =:= ru.typeOf[List[String]] => "list of list of string"
case t if t =:= ru.typeOf[Set[List[Int]]] => "list of set of list of integer"
case _ => "some other types"
} //> meth: [T](xs: List[T])(implicit evidence$5: ru.TypeTag[T])String
meth(List(,,)) //> res10: String = list of integer
meth(List("a","b")) //> res11: String = some other types
meth(List(List("a","a"))) //> res12: String = list of list of string
meth(List(Set(List(,)))) //> 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:
def meth[T: ru.WeakTypeTag](xs: List[T]) = ru.weakTypeTag[T].tpe match {
case t if t =:= ru.typeOf[Int] => "list of integer"
case t if t =:= ru.typeOf[List[String]] => "list of list of string"
case t if t =:= ru.typeOf[Set[List[Int]]] => "list of set of list of integer"
case _ => "some other types"
} //> meth: [T](xs: List[T])(implicit evidence$5: ru.WeakTypeTag[T])String
meth(List(,,)) //> res10: String = list of integer
meth(List("a","b")) //> res11: String = some other types
meth(List(List("a","a"))) //> res12: String = list of list of string
meth(List(Set(List(,)))) //> res13: String = list of set of list of integer
结果与使用TypeTag一致,好像WeakTypeTag和TypeTag没什么分别。从字面解释,WeakTypeTag应该在某些方面要求比较松。在上面的例子里调用meth函数时我们提供了一个实质类型如:List[Int],List[String],List[List[Int]]等。如果我们只能提供像List[T]这样的抽象类型的话,compiler一定会吵闹,像下面的示范:
// def foo[T] = ru.typeTag[T] //> No TypeTag available for T
def foo[T] = ru.weakTypeTag[T] //> foo: [T]=> worksheets.reflect.ru.WeakTypeTag[T]
看来WeakTypeTag可以支持抽象类型。我们再看看下面的示范:
abstract class SomeClass[T] {
def getInnerWeakType[T: ru.WeakTypeTag](obj: T) = ru.weakTypeTag[T].tpe match {
case ru.TypeRef(utype,usymb,args) =>
List(utype,usymb,args).mkString("\n")
}
val list: List[T]
val result = getInnerWeakType(list)
}
val sc = new SomeClass[Int] { val list = List(,,) }
//> sc : SomeClass[Int]
println(sc.result) //> scala.type
//| type List
//| List(T)
首先我们可以得出List[T]的内部类型就是T。更重要的是如果不使用WeakTypeTag的话getInnerWeakType(list)根本无法通过编译。
Scala Reflection - Mirrors,ClassTag,TypeTag and WeakTypeTag的更多相关文章
- Scala Macros - 元编程 Metaprogramming with Def Macros
Scala Macros对scala函数库编程人员来说是一项不可或缺的编程工具,可以通过它来解决一些用普通编程或者类层次编程(type level programming)都无法解决的问题,这是因为S ...
- Scala Macros - scalamela 1.x,inline-meta annotations
在上期讨论中我们介绍了Scala Macros,它可以说是工具库编程人员不可或缺的编程手段,可以实现编译器在编译源代码时对源代码进行的修改.扩展和替换,如此可以对用户屏蔽工具库复杂的内部细节,使他们可 ...
- Manifest 与TypeTag
Manifest和TypeTag是要解决什么问题? As with other JVM languages, Scala’s types are erased at compile time. T ...
- 了解Scala 宏
前情回顾 了解Scala反射介绍了反射的基本概念以及运行时反射的用法, 同时简单的介绍了一下编译原理知识, 其中我感觉最为绕的地方, 就属泛型的几种使用方式了. 而最抽象的概念, 就是对于符号和抽象树 ...
- 一篇入门 — Scala 宏
前情回顾 上一节, 我简单的说了一下反射的基本概念以及运行时反射的用法, 同时简单的介绍了一下编译原理知识, 其中我感觉最为绕的地方, 就属泛型的几种使用方式了. 而最抽象的概念, 就是对于符号和抽象 ...
- Scala入门到精通——第二十四节 高级类型 (三)
作者:摆摆少年梦 视频地址:http://blog.csdn.net/wsscy2004/article/details/38440247 本节主要内容 Type Specialization Man ...
- 神奇的Scala Macro之旅(三)- 实际应用
在上一篇中,我们示范了使用macro来重写 Log 的 debug/info 方法,并大致的介绍了 macro 的基本语法.基本使用方法.以及macro背后的一些概念, 如AST等.那么,本篇中,我们 ...
- 了解Scala反射
本篇文章主要让大家理解什么是Scala的反射, 以及反射的分类, 反射的一些术语概念和一些简单的反射例子. 什么是反射 我们知道, Scala是基于JVM的语言, Scala编译器会将Scala代码编 ...
- 一篇入门 -- Scala 反射
本篇文章主要让大家理解什么是Scala的反射, 以及反射的分类, 反射的一些术语概念和一些简单的反射例子. 什么是反射 我们知道, Scala是基于JVM的语言, Scala编译器会将Scala代码编 ...
随机推荐
- WebSocket - ( 一.概述 )
说到 WebSocket,不得不提 HTML5,作为近年来Web技术领域最大的改进与变化,包含CSS3.离线与存储.多媒体.连接性( Connectivity )等一系列领域,而即将介绍的 WebSo ...
- javascript函数
array.sort(function(a, b){ return a -b ; } ) 把数组 array 按照从小到大排序. [11, 22, 586, 10, -58, 86].sort(f ...
- ubuntu 下安装scrapy
1.把Scrapy签名的GPG密钥添加到APT的钥匙环中: sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 6272 ...
- 【HTML】Html页面跳转的5种方式
目录结构: // contents structure [-] html实现 javascript方式实现 结合了倒数的javascript实现(IE) 解决Firefox不支持innerText的问 ...
- SQL*Plus生成html文件
最近使用SQL*Plus命令生成html文件,遇到一些有意思的知识点,顺便记录一下,方便以后需要的时候而这些知识点又忘记而捉急.好记性不如烂笔头吗! 为什么要用SQL*Plus生成html文件? ...
- 0042 MySQL学习笔记-入门--01
基本概念: 数据库DB(database): 数据的仓库,数据的集合,是数据的一种结构化的存储 数据库管理系统DBMS(database management system): 管理数据库的一套软件 ...
- centos下开启ftp服务
如果要ftp访问linux需要安装ftp服务,vsftpd是Linux下比较好的的FTP服务器. 一.检查安装vsftp //检查是否安装vsftpd rpm -qa | grep vsftpd // ...
- Unity C#最佳实践(上)
本文为<effective c#>的读书笔记,此书类似于大名鼎鼎的<effective c++>,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针 ...
- Web API 强势入门指南
Web API是一个比较宽泛的概念.这里我们提到Web API特指ASP.NET Web API. 这篇文章中我们主要介绍Web API的主要功能以及与其他同类型框架的对比,最后通过一些相对复杂的实例 ...
- ABP(现代ASP.NET样板开发框架)系列之23、ABP展现层——异常处理
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之23.ABP展现层——异常处理 ABP是“ASP.NET Boilerplate Project (ASP.NET ...