S-JIS[2011-06-25/2011-07-01] 変更履歴

Scala using(ローンパターン)

C#では、オープンしたファイルを確実にクローズしてくれるusingという構文がある。
これと同じような事をScalaでやってみる。


using

テキストファイルを読み込む例を考えてみる。
ScalaではSourceクラスを使い、読み終わったら(あるいは途中でエラーになっても)クローズする。
この為、tryブロックで囲み、finallyでclose()を呼び出す。

val s = scala.io.Source.fromFile("C:/temp/a.txt")
try {
  s.getLines.foreach{ println }
} finally {
  s.close()
}

これを、以下のような感じで扱えるようにしたい。(usingブロックを抜けると自動的にクローズする)

using(s = scala.io.Source.fromFile("C:/temp/a.txt")) {
  s.getLines.foreach{ println }
}

ただ、Scalaでは、このような変数sを呼び出し側で自由に定義して後続ブロックで使わせることは出来ない(と思う)。
次善の策として、以下の様なusingを作ることは出来る。

def using(s: scala.io.Source)(f: scala.io.Source=>Any) {
  try {
    f(s)
  } finally {
    s.close()
  }
}

using(scala.io.Source.fromFile("C:/temp/a.txt")) { s =>
  s.getLines.foreach{ println }
}

このusingは、引数fで“Sourceを引数にとる関数”を受け取る。
呼び出す側は関数を渡す。この為、引数を受け取る変数として「s =>」を記述する必要があるが、場合によっては(プレースホルダーを使うことによって)省略できる。

using(scala.io.Source.fromFile("C:/temp/a.txt")) {
  _.getLines.foreach{ println }
}

さて、ファイルを扱うクラスはscala.io.Sourceだけではない。java.io.Readerやjava.io.OutputStreamでも最後にclose()を呼び出す必要があるので、これらでも使えるようにしたい。
JDK1.5以降の標準的なI/Oを行うクラスではCloseableインターフェースを実装しているので、それを使うと良さそう。

def using(s: java.io.Closeable)(f: java.io.Closeable=>Any) {
  try f(s) finally s.close()
}

ただ、これだと呼び出す側(関数fを指定する側)も“Closeableとして”しか扱えないので、ジェネリクス化しておく方がいい。

def using[A <% java.io.Closeable](s: A)(f: A=>Any) {
  try f(s) finally s.close()
}

と思ったら、scala.io.SourceはCloseableを実装(ミックスイン)していなかった^^;
しかしScalaでは構造的部分型が使えるので、「close()を持っているクラス」という指定の仕方が出来る。

def using[A <% { def close():Unit }](s: A)(f: A=>Any) {
  try f(s) finally s.close()
}

ローンパターン

このusingのような構造は、ローンパターン(Loan pattern)と呼ばれている。
ローンと言えば、借金。ローンパターンは、リソースを貸し出す(あるいは借りる)という意味らしい。

using(リソース作成) {
  リソース使用
}

using呼び出しのブロック(関数)でリソースを使用する。リソースの管理(最後のクローズ)自体はusing関数が(内部で)行う。
using関数が呼び出し側に対してリソースを貸し出している、という意味合いなのかな?


このローンパターン(自動的にクローズ(後処理)する方法)については色々な人が実装を考案していて、細部は異なる。
(Scala標準でusingのような関数を用意してくれればいいのに^^;)

出典 実装(引用) 備考
Scala Wiki
def using[A, R <: Resource](r : R)(f : R => A) : A =
    try {
        f(r)
    } finally {
        r.dispose()
    }
Resourceはdispose()メソッドを持っている想定。
このusingでは、戻り値も型パラメーターAで指定されている。
Scala Wiki
def withResource[A](f : Resource => A) : A = {
    val r = getResource()
    try {
        f(r)
    } finally {
        r.dispose()
    }
}
リソース生成のgetResouce()を別途用意するようにする事で、
リソース生成用の引数を無くしている。
okomokさんのloan pattern in cps
def use[A <: java.io.Closeable](x: A): A @suspendable = {
    shift { (k: A => Unit) =>
        try {
            k(x)
        } finally {
            x.close()
        }
    }
}
scala.util.continuationsを扱う為のメソッドらしい。
yuroyoroさんのretry機能付きLoan pattern
def retry[T](p: => Boolean)(f: => T):T =
  try{ f } catch { case e => if(p) retry(p)(f) else throw e }
例外が起きたら引数pによるリトライ判定を行う。
リトライ実行部分が再帰呼び出しになっている所が格好いい!
応用してリトライ回数指定とか、簡単に出来そう。
xuweiさんのscala勉強会の、今までの参加者一覧と、参加回数の取得script
def using[A <: {def close()},B](resource:A)(func:A => B):Option[B] =
  try{
    Some( func(resource) ) //成功したら、Someに包んで返す
  }catch{
    case e:Exception => e.printStackTrace
    None //失敗したら、ログ吐いて、None返す
  }finally{
    if(resource != null) resource.close()
  }
戻り値をOptionで包んで、例外が発生した場合はNoneを返している。
xuweiさんのリソースの処理
def using[A,B]( resource:A )( endAction : A => B )( func: A => Unit ){
  try{
    func(resource)
  }catch{
    case e:Exception => e.printStackTrace
  }finally{
    endAction(resource)
  }
}
finallyで行う処理も渡せるようになっているバージョン。
akihiro4chawonさんのぼくのかんがえる、#scala で最強の using だお!
def using[A, B](resource: A)(func: A => B)(implicit closer: Closer[A]) =
  try {
    func(resource)
  } finally {
    closer(resource)
  }

trait Closer[-A] {
  def apply(resource: A)
}

object Closer {
  def apply[A](f: A => Unit) = new Closer[A] {
    def apply(resource: A) = f(resource) 
  }
}

implicit val IoSourceCloser = Closer[io.Source]      (_.close)
implicit val disposer       = Closer[{def dispose()}](_.dispose)
implicit val closer         = Closer[{def close()}]  (_.close)
close処理を行うクラスを用意しておき、
暗黙の引数として渡せるようになっている。
あおいさんのC#のusing構文をScalaで    
Unfilteredのio.scala    
Scala実践プログラミング』p.280   暗黙の変換を使って、close()にもdispose()にも対応している。
がくぞーさんのLoanパターンをfor式で使えるようにしてみたよ
class Loan[T <: {def close()}] private (value: T) {

  def foreach[U](f: T => U): U = try {
    f(value)
  } finally {
    value.close()
  } 

}
for式を使って複数のリソースをオープンできる。[2011-06-30]
がくぞーさんのLoanパターンのアレに Conceptパターンを足してみたよ   上記のものに暗黙の変換を追加してrelease()やdispose()にも対応したもの。[2011-07-01]

Scala実験へ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま