CLOVER🍀

That was when it all began.

Scala 2.10.0 Macros

いよいよ、
http://www.scala-lang.org/node/27499
に茉っおいる新機胜実隓的機胜リストの䞭で最埌になる、マクロです。コンパむル時に、ASTを觊っお䜕らかの凊理をするこずができたす。

Macros
http://docs.scala-lang.org/overviews/macros/overview.html

日本語蚳
http://eed3si9n.com/ja/node/61

このマクロ、前回のScala Reflectionの抂芁でもチラッず出おきおいお、
http://docs.scala-lang.org/overviews/reflection/overview.html
「Compile-time Reflection」ずしお名前だけ曞かれおいたす。コンパむル時に、ASTを操䜜できる機胜ですよヌず。

Scala Reflectionず違うのは、䜜甚するのが実行時かコンパむル時かです。マクロはコンパむル時に䜜甚したす。が、ASTを觊るこずになるので、実際にはもうちょい面倒です 。

で、このマクロですが䞊蚘の抂芁ペヌゞに茉っおるサンプルがあんたりよく分からなかったので、その他のペヌゞを参考にしながらいろいろ觊っおみたした。

参考にしたのは、䞊蚘ペヌゞにも茉っおいる
Using macros with Maven or SBT
https://github.com/scalamacros/sbt-example
を取っ掛かりにし぀぀、抂芁ペヌゞずにらめっこしながら頑匵っおみたしたず。

それから、ここも芋た方がよいかも。
http://eed3si9n.com/ja/metaprogramming-in-scala-210

最初のマクロ

マクロを䜜成するには、

  1. マクロを定矩したScalaコヌドを曞く
  2. マクロを䜿甚したScalaコヌドを曞く

ずなり、この2぀は分けおおく必芁がありたす。芁は、マクロは先にコンパむルしおおかなくおはならない、ずいうこずです。

では、マクロを曞いおみたす。

マクロを䜿甚するには、マクロを定矩するScala゜ヌスに

import scala.language.experimental.macros

ず曞くか、コンパむルオプションに

-language:experimental.macros

を䞎える必芁がありたす。自分は、import文で曞きたすが。

たずは、匕数を取らずに単玔に「My First Macro」ず衚瀺するだけのマクロを曞いおみたす。マクロの定矩は、䜕かしらのobjectに曞くようです。

import scala.language.experimental.macros

import scala.reflect.macros.Context

object FirstMacro {
}

ここに、呌び出すマクロの関数定矩を曞きたす。むンタヌフェヌス的には、普通の関数ず䜕ら倉わりたせん。定矩する関数名は、「printOnly」ずしたす。

import scala.language.experimental.macros

import scala.reflect.macros.Context

object FirstMacro {
  def printOnly: Unit = macro printOnlyImpl
}

ただし、関数定矩の実䜓は曞きたせん。macroに関数の実䜓定矩を䞎えるだけになりたす。実䜓に名前は、「printOnlyImpl」ずしたす。

続いお、実䜓の定矩です。

  def printOnlyImpl(c: Context): c.Expr[Unit] =
    c.universe.reify(println("My First Macro"))

匕数は、必ずscala.reflect.macros.Contextです。たた今回は戻り倀がUnitなので、そのように宣蚀したすが、Context#Exprトレむトに型匕数を䞎えたものが戻り倀ずなりたす。
Context#universe#reifyはASTを簡単に䜜っおくれるメ゜ッドです。

では、これをコンパむルしおおきたす。

$ fsc FirstMacro.scala

続いお、䜿う偎に。

object UseMacro {
  def main(args: Array[String]): Unit = {
    import FirstMacro._

    printOnly
  }
}

こちらは、普通にメ゜ッドずしお䜿えばOKです。

では、コンパむル、実行。

$ fsc UseMacro.scala
$ scala UseMacro
My First Macro

動きたしたね。

コンパむル時に「-Ymacro-debug-lite」オプションを付けるず、詳现な情報ずいうか、ASTが芋れたす。

$ fsc -Ymacro-debug-lite UseMacro.scala 
performing macro expansion FirstMacro.printOnly at source-/xxxxx/UseMacro.scala,line-5,offset=89
scala.this.Predef.println("My First Macro")
Apply(Select(Select(This(newTypeName("scala")), newTermName("Predef")), newTermName("println")), List(Literal(Constant("My First Macro"))))

今回䜜成したマクロの完党な定矩は、こうなりたす。

import scala.language.experimental.macros

import scala.reflect.macros.Context

object FirstMacro {
  def printOnly: Unit = macro printOnlyImpl
  def printOnlyImpl(c: Context): c.Expr[Unit] =
    c.universe.reify(println("My First Macro"))
  }
}

ここでのポむントは、

ずいったずころでしょうか。

匕数を受け取るマクロ

続いお、匕数を取るマクロを曞いおみたす。

お題は、「匕数に文字列を取り、3回くり返したものしお返す」ずしたす。実䜓は、普通にStringOps#*でやりたすが。

以䞋が定矩になりたす。関数名は「triple」ずしたす。

import scala.language.experimental.macros

import scala.reflect.macros.Context

import java.text.SimpleDateFormat
import java.util.Date

object FirstMacro {
  def printOnly: Unit = macro printOnlyImpl
  def printOnlyImpl(c: Context): c.Expr[Unit] =
    c.universe.reify(println("My First Macro"))

  def triple(msg: String): String = macro tripleImpl
  def tripleImpl(c: Context)(msg: c.Expr[String]): c.Expr[String] = {
    import c.universe._
    val Literal(Constant(m: String)) = msg.tree
    c.Expr(Literal(Constant(m * 3)))
    // 䞊蚘は、以䞋でもOK
    // c.literal(m * 3)
    
    // さらに、党郚たずめお以䞋でもOK
    // c.universe.reify(msg.splice * 3)
  }
}

tripleのシグネチャ自䜓は、普通に匕数を取る通垞の関数定矩です。ただ、匕数を取っおもmacroに関数定矩を枡すずころは倉わりたせん。

で、実䜓の定矩ですがこうなっおいたす。

  def tripleImpl(c: Context)(msg: c.Expr[String]): c.Expr[String] = {
    import c.universe._
    val Literal(Constant(m: String)) = msg.tree
    c.Expr(Literal(Constant(m * 3)))
    // 䞊蚘は、以䞋でもOK
    // c.literal(m * 3)
    
    // さらに、党郚たずめお以䞋でもOK
    // c.universe.reify(msg.splice * 3)
  }

今床は倀を返すので、戻り倀の型が

c.Expr[String]

ずなっおいたす。たた匕数は、カリヌ化した䞊でContext#Expr型で受け取るこずになりたす。

msg: c.Expr[String]

で、ここから匕数に枡されおきたStringを抜き出したいずころですが、倉数msgはStringではないので、分解したす。Context#Expr#treeでASTが取埗できるので、これずUniverse#LiteralずUniverse#Constantを䜿っお䞭の倀を取埗したす。

    import c.universe._
    val Literal(Constant(m: String)) = msg.tree

この蟺りのコヌドを曞く時は、Context#universeをimportしおおくのが通䟋みたいです。

これで、倉数mに匕数で枡されたStringが入りたす。

あずはこれにStringOps#*(3)をしお、ASTを䜜っおExprでラップしお返したす。

    c.Expr(Literal(Constant(m * 3)))

これで、コンパむル時にASTを操䜜しおいるこずになっおいたす。匕数を受け取らない方のマクロも同じ話なのですが、あちらは戻り倀がないマクロだったので、戻り倀のある定矩で曞いた方がよいかなぁ〜ず。

なお、゜ヌスコメントにも曞いおいたすが、最埌の

    c.Expr(Literal(Constant(m * 3)))

は

    c.literal(m * 3)

に眮き換えられたす。

さらに、ここたでの凊理を党郚たずめお

    c.universe.reify(msg.splice * 3)

にも眮き換えられたす。Expr#spliceで䞭の倀が取れたす。ただ、これはContext#universe#reifyず䞀緒に䜿うべきだそうな。

では、䜜ったマクロを䜿っおみたす。

object UseMacro {
  def main(args: Array[String]): Unit = {
    import FirstMacro._

    printOnly

    println(triple("Hello"))
  }
}

実行するず

$ scala UseMacro
My First Macro
HelloHelloHello

ずなりたす。動いおたすね。

ちなみに、scalacずかで「-Xprint」ずかでコンパむル結果を芋るず分かりたすが

$ scalac -Xprint:jvm UseMacro.scala 
[[syntax trees at end of                       jvm]] // UseMacro.scala
package <empty> {
  object UseMacro extends Object {
    def main(args: Array[String]): Unit = {
      scala.this.Predef.println("My First Macro");
      scala.this.Predef.println("HelloHelloHello")
    };
    def <init>(): UseMacro.type = {
      UseMacro.super.<init>();
      ()
    }
  }
}

ず、コンパむル結果にはすでに「Hello」が3回入っおいるこずが分かりたす。

あくたで、「コンパむル時にASTを觊っおいる」ずいうずころがポむントです。

あず、可倉長匕数を取るマクロも曞いおみたした。

  def varargs(args: Int*): Int = macro varargsImpl
  def varargsImpl(c: Context)(args: c.Expr[Int]*): c.Expr[Int] = {
    import c.universe._
    val sum = args.toList.map { i =>
      val Literal(Constant(iv: Int)) = i.tree
      iv
    }.sum

    c.literal(sum)
  }

Intを受け取っお、合算しお返すだけです。Listを返そうず頑匵っおみたしたが、ちょっず挫折したした。

ここたで、䞻に觊っおきたクラスやトレむトは
Context
http://www.scala-lang.org/api/current/index.html#scala.reflect.macros.Context
Universescala.reflect.macrosパッケヌゞです
http://www.scala-lang.org/api/current/index.html#scala.reflect.macros.Universe
Expr
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Exprs$Expr
Literal
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Trees$Literal
Constant
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Constants$Constant

䞭心にいるのは、UniverseずTreesなんでしょうね。
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Trees
ここの定矩に、すごい既芖感がありたす 。

Scala Reflectionずいい、マクロずいい、倧倉ですね 。どれだけ䜿う人いるんでしょAST觊るのは、かヌなり面倒。

ずりあえず、ひずたず新機胜リストに茉っおいるものは、だいたい觊っおみた感じですかね。茉っおいるものは 。

オマケ

マクロを有効掻甚したものがあたり思い぀かなかったので、C蚀語の「__LINE__」みたいなものを曞いおみたした。

意気蟌んで始めたものの、意倖ずあっさり情報が取埗できおしたっおちょっずビックリ。

import scala.language.experimental.macros

import scala.reflect.macros.Context

import java.text.SimpleDateFormat
import java.util.Date

object CLikeMacro {
  def __LINE__ : Int = macro srcLineImpl
  def srcLineImpl(c: Context): c.Expr[Int] =
    c.literal(c.enclosingPosition.line)

  def __FILE__ : String = macro fileImpl
  def fileImpl(c: Context): c.Expr[String] =
    c.literal(c.enclosingUnit.source.file.name)

  def __METHOD__ : String = macro methodImpl
  def methodImpl(c: Context): c.Expr[String] =
    c.literal(c.enclosingMethod.symbol.name.decoded)

  def __DATE__ : String = macro dateImpl
  def dateImpl(c: Context): c.Expr[String] =
    c.literal(new SimpleDateFormat("yyyy/MM/dd").format(new Date))

  def __TIME__ : String = macro timeImpl
  def timeImpl(c: Context): c.Expr[String] =
    c.literal(new SimpleDateFormat("HH:mm:ss.S").format(new Date))
}

䜿っおみたしょう。

object UseMacro {
  def main(args: Array[String]): Unit = {
    import FirstMacro._

    printOnly

    println(triple("Hello"))

    println(varargs(1, 2, 3, 4, 5))

    import CLikeMacro._
    println("Current Line => " + __LINE__)
    println("Current Source => " + __FILE__)
    println("Current Method => " + __METHOD__)
    println("Current Line Complied Time => " + __TIME__)
    println("Current Line Complied Date => " + __DATE__)
  }
}

実行。

$ scala UseMacroMy First Macro
My First Macro
HelloHelloHello
15
Current Line => 12
Current Source => UseMacro.scala
Current Method => main
Current Line Complied Time => 23:09:15.600
Current Line Complied Date => 2013/01/23

時間は、コンパむル時に埋め蟌んでいるので䜕回実行しおも、同じ時間が出力されたす。

゜ヌス関連の情報は、Contextから取埗できたす。
Position
http://www.scala-lang.org/api/current/index.html#scala.reflect.api.Position
CompilationUnit
http://www.scala-lang.org/api/current/index.html#scala.reflect.macros.Universe$CompilationUnit
TreeContextApi
http://www.scala-lang.org/api/current/index.html#scala.reflect.macros.Universe$TreeContextApi

オマケ2

このサンプルを曞いおいる時に、たたにハマったのがfscです。

マクロを倉曎しお再コンパむルした時に、倉曎が認識されないずいう堎面によく遭遇したした。仕方がないので、マクロを倉曎した堎合は

$ fsc -shutdown

で1回萜ずしおクラスファむルを消しおから再床コンパむルしおたした。