Takuya71 のぶろぐ

外資系ソフトウェア会社で働いてます、認定スクラムマスター

Scala Macro を試す その2

Javaのファイルの処理の部分をマクロで

前回 試した マクロですが、使える状況なにか無いか考えていたのですが、私の理解ではマクロはコンパイル時にソースを置き換えてくれるもの と理解してます。
そこで 思ったのが scala でプログラム書いていて ファイルの操作をするような場合に java の PrinteWriter とか使うのですが new … new … のようになり冗長的だと思っていて別の関数書いておいたりしているのですが これって マクロでも出来るのではとおもって試してみました。

普段はこんな感じでファイル扱う時にJavaのクラス使うときあります。

val f1 = new java.io.File("hogehoge.txt")
val f2 = new java.io.File("output.txt")

val r1 = new java.io.BufferedReader( new java.io.FileReader(f1)

val w1 = new java.io.PrintWriter(new java.io.BufferedWriter( new java.io.FileWriter(f2)))

これをマクロを使って コンパイル時にこの記述に展開してくれる。 file と readFile と printWriter ってのをマクロとして定義してみます。 reify{中身} の中が置き換えられる内容です。

def file(param: String) = macro file_impl
def file_impl(c: Context)(param:c.Expr[String]): c.Expr[java.io.File] = {
  import c.universe._
  reify{new java.io.File(param.splice)}
}

def readFile(param: java.io.File) = macro readFile_impl
def readFile_impl(c: Context)(param:c.Expr[java.io.File]) = {
  import c.universe._
    reify{new java.io.BufferedReader( new java.io.FileReader(param.splice))}
}
    
def printWriter(param: java.io.File) = macro printWriter_impl
def printWriter_impl(c: Context)(param:c.Expr[java.io.File]) = {
  import c.universe._
  reify{new java.io.PrintWriter(new java.io.BufferedWriter( new java.io.FileWriter(param.splice)))}
}

こういうマクロを定義しておくと
プログラムの方では

 val f1 = Macros.file("hogehoge.txt")
    val f2 = Macros.file("output.txt")
    val r1 = Macros.readFile(f1)
    val w1 = Macros.printWriter(f2)
    try {
        var line: String = null
        while ({line = r1.readLine; line != null}){
            w1.println(line)
          println(line)
        }
    } finally {
        r1.close
        w1.close
    }

のように 短く書けます。

また 引数の型によって 展開されるものを変えることも出来るようです
readFile に String の引数が渡されたときでも動作するようにしてみたいと思います。
マクロの定義は 以下のようにします。

def readFile(param: java.io.File) = macro readFile_impl
def readFile_impl(c: Context)(param:c.Expr[java.io.File]) = {
 import c.universe._
 reify{new java.io.BufferedReader( new java.io.FileReader(param.splice))}
}

// String を取るマクロ定義を追加
def readFile(param: String) = macro readFileString_impl
def readFileString_impl(c: Context)(param:c.Expr[String]) = {
  import c.universe._
  reify{new java.io.BufferedReader( new java.io.FileReader(new java.io.File(param.splice)))}
}

マクロ定義の def は readFile() の名前は同じですが 引数の型と implimentの方の名前を変えます。 これで readFile(java.io.File型) の場合も readFile(String型) の場合も コンパイル時に 型のあっているほうに置き換えられてコンパイルされるようです。 コンパイル時に型のチェックもされるので いいかんじ。

マクロ面白くなってきました。

source

Macros.scala

def file(param: String) = macro file_impl
def file_impl(c: Context)(param:c.Expr[String]): c.Expr[java.io.File] = {
  import c.universe._
  reify{new java.io.File(param.splice)}
}
    
def readFile(param: java.io.File) = macro readFile_impl
def readFile_impl(c: Context)(param:c.Expr[java.io.File]) = {
  import c.universe._
  reify{new java.io.BufferedReader( new java.io.FileReader(param.splice))}
}

def readFile(param: String) = macro readFileString_impl
def readFileString_impl(c: Context)(param:c.Expr[String]) = {
  import c.universe._
  reify{new java.io.BufferedReader( new java.io.FileReader(new java.io.File(param.splice)))}
}
    
def printWriter(param: java.io.File) = macro printWriter_impl
def printWriter_impl(c: Context)(param:c.Expr[java.io.File]) = {
  import c.universe._
  reify{new java.io.PrintWriter(new java.io.BufferedWriter( new java.io.FileWriter(param.splice)))}
}
    
def printWriter(param: String) = macro printWriterString_impl
def printWriterString_impl(c: Context)(param:c.Expr[String]) = {
  import c.universe._
  reify{new java.io.PrintWriter(new java.io.BufferedWriter( new java.io.FileWriter(new java.io.File(param.splice))))}
}

macrosample.sacala

val f1 = Macros.file("hogehoge.txt")
val r1 = Macros.readFile(f1)
val w1 = Macros.printWriter("output.txt") // ファイル名の文字列で渡す。
                                          // printWriterString_impl が展開される。
try {
  var line: String = null
  while ({line = r1.readLine; line != null}){
    w1.println(line)
    println(line)
  }
} finally {
  r1.close
  w1.close
}