Scalaマクロ Tips: Enclosing 2013年10月15日

今回はマクロの周辺の情報を取得できるEnclosingについてです。

Scalaのマクロはマクロが使われた場所の位置や、どのクラスで使われたか、どのメソッドで使われたかなどの情報を取得することができます。 それらはscala.reflect.macros.Contextのenclosingを頭に持つメソッドから取得することができます。

enclosingPosition

enclosingPositionはマクロが使われたソースコード上の位置を教えてくれます。

// Enclosing.scala
package com.github.hexx

import scala.reflect.macros.Context

object Enclosing {
  def enclosingPosition = macro enclosingPositionImpl

  def warning = macro warningImpl

  def enclosingPositionImpl(c: Context): c.Expr[(Int, Int)] = {
    import c.universe._
    val p = c.enclosingPosition
    reify((c.literal(p.line).splice, c.literal(p.column).splice))
  }

  def warningImpl(c: Context): c.Expr[Unit] = {
    c.warning(c.enclosingPosition, "Don't mind this message.")
    c.literalUnit
  }
}
// Main.scala
import com.github.hexx._

object Main extends App {
  println(Enclosing.enclosingPosition)
  println(Enclosing.warning)
}

これを実行すると(5,21)と出力されます。これは5行目の21文字目ということですね。 ちょうどMain.scalaのenclosingPositionが使われた位置になっています。 普通はあまり使い道がないと思うのですが、コンパイル時のチェックでエラーや警告を出すときに使います。

このコードをsbt上でcleanしてcompileしてみると、

[warn] /home/seitaro/git/scala-macro-tips/src/main/scala/Main.scala:6: Don't mind this message.
[warn]   println(Enclosing.warning)
[warn]                     ^
[warn] one warning found

という警告が出ます。Enclosing.warningのメッセージです。 Scalaのマクロの用途として静的(コンパイル時)チェックの強化があるのですが、 そのときにabortやwaringと一緒に使います。

enclosingClass, enclosingMethod, enclosingUnit

enclosingClass、enclosingMethod、enclosingUnitは、それぞれマクロが使われたクラス、メソッド、ソースコードのファイルの構文木を取得することができます。

// Enclosing.scala
object Enclosing {
  def enclosingTrees = macro enclosingTreesImpl

  def enclosingTreesImpl(c: Context): c.Expr[(String, String, String)] = {
    import c.universe._
    val k = c.literal(c.enclosingClass.toString)
    val m = c.literal(c.enclosingMethod.toString)
    val u = c.literal(c.enclosingUnit.body.toString)
    reify((k.splice, m.splice, u.splice))
  }
}
// Main.scala
import com.github.hexx._

object Main extends App {
  def method = Enclosing.enclosingTrees
  val (classTree, methodTree, compileUnitTree) = method
  println(classTree)
  println(methodTree)
  println(compileUnitTree)
}

このコードを実行すると

object Main extends App {
  //中略
}
def method = Enclosing.enclosingTrees
package <empty> {
  import com.github.hexx._;
  object Main extends App {
    // 中略
  }
}

みたいな出力になります。それぞれマクロの中からクラス、メソッド、ソースコードのファイルの構文木が取得できているのがわかると思います。

enclosingImplicits

enclosingImplicitsはマクロをimplicitとして定義して使われたときに、その呼び出された構文木を取得できることができます。 言葉で言っても難しいのでコードで説明します。

package com.github.hexx

import scala.reflect.macros.Context

object Enclosing {
  implicit def enclosingImplicits = macro enclosingImplicitsImpl

  def enclosingImplicitsImpl(c: Context): c.Expr[String] = {
    c.literal(c.enclosingImplicits.mkString)
  }
}

まずimplicitとしてマクロを定義します。

object Main extends App {
  import com.github.hexx.Enclosing._
  def method(i: Int)(implicit s: String) = println(s)
  method(123)
}

そして、このマクロをimplicit parameterとして呼び出されるように使います。 このコードを実行すると(String,Main.this.method(123))と表示されます。 つまりimplicit parameterの側から普通の引数などにアクセスできるわけです。 なかなかマニアックな機能だと思いますが、マクロでしかできないことができそうな気がします。

今回はEnclosingについて説明しました。 局所的なマクロから普通の関数では知りえないような色々な情報を取得できるのがわかったと思います。 活用すると力技で色々なことができそうな気がしますね。

今回説明した内容のサンプルコードは

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

にあります。

次はWeakTypeTagあたりですかね。