Scalaマクロ Tips: Prefix 2013年10月14日

まだまだ使っている人が少ないと思われるScalaのマクロについて、何回かにわけてTipsを書いていきたいと思います。

最初はPrefixについてです。

Scalaのマクロはコンパイル時に対象の構文木(引数で渡されるものやアノテーションが付けられたもの)を変換して別の構文木にするわけですが、その対象の構文木以外にも様々な情報を取得することができます。 今回解説するPrefixはどういう形でマクロが呼び出されたかという情報を取得することができます。

Prefixについて三つのマクロを作ってみました。

package com.github.hexx

import scala.reflect.macros.Context

trait Prefix {
  def prefix = macro Prefix.prefixImpl
  def prefixTree = macro Prefix.prefixTreeImpl
  def prefixHello = macro Prefix.prefixHelloImpl
}

object Prefix {
  def prefixImpl(c: Context): c.Expr[Any] = {
    import c.universe._
    c.prefix
  }

  def prefixTreeImpl(c: Context): c.Expr[String] = {
    import c.universe._
    c.literal(showRaw(c.prefix.tree))
  }

  def prefixHelloImpl(c: Context): c.Expr[String] = {
    import c.universe._
    c.Expr[String](q"${c.prefix.tree}.hello")
  }
}

それぞれ使ってみましょう。

class Person(name: String, age: Int) {
  def hello = s"Hello, $name"
}

object Main extends App {
  val p = new Person("hogeika", 13) with Prefix

  println(p == p.prefix)
  println(p.prefixTree)
  println(p.prefixHello)
}

これを実行すると、

true
Select(This(newTypeName("Main")), newTermName("p"))
Hello, hogeika

と出力されます。つまりマクロのPrefixというのは普通のメソッドの中のthisのように使うことができるというのがわかると思います。 じゃあマクロでもthisと書いたらいいのではないかと思われるかもしれませんが、このマクロでthisと書いてしまうとpではなくobject Mainのほうが参照されてしまいます。 なのでマクロの中でthisのようなものを使いたい場合はPrefixの出番になるというわけです。

注意するべき点はPrefixはあくまで呼び出された構文木を指すということです。たとえば(new Person("hogeika", 13) with Prefix).prefixTree

Block(List(ClassDef(Modifiers(FINAL), newTypeName("$anon"), List(), Template(List(Ident(Person), TypeTree().setOriginal(Select(Select(Select(Ident(com), com.github), com.github.hexx), com.github.hexx.Prefix))), emptyValDef, List(DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(newTypeName("$anon")), tpnme.EMPTY), nme.CONSTRUCTOR), List(Literal(Constant("hogeika")), Literal(Constant(13))))), Literal(Constant(())))))))), Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List()))

というそのままの複雑な構文木に変換されます。通常はどのような形でマクロが呼び出されるかはわからないので、Prefixの構文木自体を解析することはせずに、Prefixを丸ごと使うようにしたほうがいいと思います。

今回はPrefixの解説をしました。まだまだマクロから取得できる情報は色々あるので次回以降で紹介していきたいと思います。

今回の記事のそのまま動作するコードは

https://github.com/hexx/scala-macro-tips

にあります。