Scala の動的変数で DSL を簡潔にする [Scala]
Ruby のブロックつき open のように勝手に後始末をしてくれる open メソッドを Scala で実装することを考えて見ます。これを素直に書けばこうなります。
import java.io.FileOutputStream
object DisposableFile1 {
def openOut(path: String)(block: FileOutputStream => Unit) {
val out = new FileOutputStream(path)
try {
block(out)
} finally {
out.close()
// Console.println("closed")
}
}
}
これはこのようにして使えます。
scala> import DisposableFile1._ import DisposableFile1._ scala> openOut("d1.txt") { (out:FileOutputStream) => | out.write("hello".getBytes) | }
何か問題はあるでしょうか。特になさそうです。Ruby もこんな感じです。ただ、こうしたブロックの中で操作されるファイルというのは普通 openOut メソッドで開いたファイルに決まっています。何故いちいちブロック内で out 変数を使わなければいけないのでしょうか。
こうした場合役に立つのが Scala で標準装備されている DynamicVariable クラスです(以前書いた記事 [1] ではこれが標準装備だと知らずに自分で似たようなコードを実装していましたが)。これを使うと先のコードは以下のように書き換えられます。
import java.io._
import scala.util.DynamicVariable
object DisposableFile2 extends OutputStream {
val out = new DynamicVariable[FileOutputStream](null)
def openOut(path: String)(block: => Unit) {
val out_ = new FileOutputStream(path)
try {
out.withValue(out_) { block }
} finally {
out_.close()
// Console.println("closed")
}
}
def write(b: Int) = out.value.write(b)
override def write(b: Array[Byte]) = out.value.write(b)
override def write(b: Array[Byte], off: Int, len: Int) = out.value.write(b, off, len)
override def flush = out.value.flush
}
使い方はこうです。
scala> import DisposableFile2._ import DisposableFile2._ scala> openOut("d2.txt") { | write("hello".getBytes) | }
DisposableFile2 で定義している write メソッドなどは常に DisposableFile2 オブジェクトの out 変数を参照して処理を委譲していますが、この out 変数は DynamicVariable クラスのインスタンスで、その内容(value で取得できる)は動的に変化します。
DynamicVariable クラスの withValue メソッドはある値とブロックをとり、渡された値で動的変数の内容を一時的に改変してブロックを実行します。ブロックの実行が終わると動的変数の値は元に戻ります。つまり Perl の local や Common Lisp の special variable のような動的スコープを実現しているわけです。
ブロック実行後に内容が元に戻るわけなので以下のようなネストも可能です。この場合 d3.txt の内容は "hello world" になります。
openOut("d3.txt") { write("hello".getBytes) openOut("d4.txt") { write("howdy".getBytes) } write(" world".getBytes) }
動的変数をうまく使うと Scala で DSL 的なことをする場合に構文を簡潔にすることができると思います。ちなみに Scala の Console オブジェクトの withIn メソッドや withOut メソッドでも内部的に DynamicVariable が使われています。
[1] http://blog.so-net.ne.jp/rainyday/2007-06-17-1
コメント 0