scala 的 reflection 与 macro 初探

scala 的 reflection API 不仅仅包括 runtime 的行为,还允许在 compile-time 使用这些 API 生成一些代码,这就是 scala 在 2.10 实验性的 feature,macro。在 reflection 的 API 里面有几个重要的概念:

  • 为了将 runtime/compile-time 的环境区分开来,scala 提供了两个不同 universe(scala.reflect.runtime.universe 和 scala.reflect.macros.universe),他们里面提供了一些相似的东西用来完成对应 universe 里面的 reflection 问题
  • 在每个 universe 里面都存在一批 mirrors,不同类型的 mirror 可以帮助我们拿到不同类型的元素,比如获得类对象,获得方法对象或者获得成员等等
  • 在 runtime universe 里面我们有:InstanceMirror、MethodMirror、FieldMirror、ClassMirror 和 ModuleMirror(具体用例可参看这里
  • Symbol 是建立 Name 和实体(如类、方法、成员)之间关系的概念,Symbol 之间存在一个树状结构,这就是传说中的 AST
  • Symbol 分两类,一个是表示类型的 TypeSymbol(如 class、object 等),一个是表示实体的 TermSymbol(方法、成员、变量)
  • 正是因为 Symbol 的结构,你可以通过它访问到其子节点,通常可以通过某个类的 Symbol 的 Type,member 方法拿到子 Symbol,拿到了 Symbol 之后一般需要转换成为具体的 Symbol 类型,如 asMethod、asClass 等
  • 所谓的 Type 是指 Symbol 的类型,通过 typeOf 方法作用在 Symbol 上就会获得对应的 Type,表示继承关系可以用对应 Type 通过 <:< 来判别;除了 member/members 以外,还有 declaration 获得这个部分自己声明的成员(区别在于 memer 获得的是包括父类定义的所有 Symbol 而 declaration 只拿到本类自己定义的部分)
  • 所谓的 Tree 就是 scala 的 AST,在 scala 里面可以通过 annotation 的参数(也就是被 annotate 的东西)、reify 和 macro 拿到一些 AST,简单的说 Tree 有三种对应于树的叶子的 TermTree、表示类型的 TypTree 和 SymTree
  • Tree 可以 show 和通过 pattern matching 来做 traverse(总觉得这块跟 boost.proto 有点像)
  • Name 可以看成是尚未关联实体的 Symbol,也就是说只是一个字符串的“别称”
  • 每个 AST 可以认为是 Exprs 的子类,其中类似 private 等称为 Flag,另外还有一些是常值的就是 Constant
  • reflection API 提供了 type tags,可以认为是之前 Manifest 的扩展,这部分可以看成是由于 type erasure 造成类型丢失后通过 library 补充回去的一个技术,通常可以用 typeTag、classTag 获得。将来这部分可能替换掉 Manifest

有了这些基本概念,上次提到的 Delegate 其实可以改进

trait Delegate[T] extends Dynamic {
  var delegate: T = _

  import scala.reflect.runtime.{universe => ru}
  def applyDynamic(method: String)(args: Any*)(implicit tTag: TypeTag[T], cTag: ClassTag[T]) = {
    val m = ru.runtimeMirror(delegate.getClass.getClassLoader)
    val n = ru.newTermName(method)
    val sym = ru.typeTag[T].tpe.member(n).asMethod
    val im = m.reflect(delegate)
    val methodMirror = im.reflectMethod(sym)
    methodMirror.apply(args:_*)
  }
}

那么这里继续讨论 macro,就其目的,在编译时获得 reflection API 可以干什么呢?通常认为是 code generation、static check 与 DSL。下面几个具体的例子

  • code generation 主要解决的是 boilerplate,也就是一些经常反复的结构,比如很多 logging 相关的都会写 if debug enabled 然后干什么
  • static check 意味着类似 C 的 printf 语法原先需要运行时检查类型,通过 macro 可以做到编译时类型检查
  • DSL 本质上也是一个 AST

macro 实现函数写起来和一个正常的函数类似,但是主体部分以 macro implMacro 结束,而 implMacro 是 macro 的实现部分,这个是一个 curried function,第一部分是 Context 拿到调用的上下文信息,第二部分是前面函数的定义,但是原先每个参数类型被替换成为 Expr[T],实现一般会返回一个 AST,比如通过 reify 获得,这样通过编译器展开后得到的就是一个被替换过的 AST 了。macro 处理异常往往通过 Context 输出 error/info 等信息。2.10 仅仅支持函数的 macro 不支持 bundle。

  • macro 可以用在 implicit 里面,如实现 materializer 通过 reflection 拿到对应类的成员就可以实现通用的 materializer;
  • macro annotation 意味着通过自己设计的 annotation 和 macro 可以为一些类型生成东西,比如添加 Java 的 getter/setter,实现起来比较麻烦,不过提供了所谓的 quasiquote 来帮助简化这个过程

从某种角度来说 macro 算是一个比较高端的 language feature 试水,想法比较直接,用起来对一般程序员来说还是有一定的难度,但愿这些概念的理解能够帮助后面学会这种程序的编写。

——————
And Leah said, Happy am I, for the daughters will call me blessed: and she called his name Asher.

Advertisements
scala 的 reflection 与 macro 初探

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s