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 }