Safe
エフェクトはたとえ例外が発生したときでもクローズされなければいけないリソースを扱うときに便利だ。主な操作は、
finally
は、他の操作が成功だろうと失敗だろうとそのあとに必ず実行されなければならないアクションを作り出す。
catchThrowable
は、発生した例外を処理する
bracket(open)(step)(close)
は、リソースをオープンし、それを使い、最後に安全にクローズする。close
の部分は「ファイナライザー」だ。
リソースの保護の例を見てみよう。
import org.atnos.eff.syntax.all._
import org.atnos.eff._, all._
// 「使用中」になるリソースを表してみよう
case class Resource(values: List[Int] = (1 to 10).toList, inUse: Boolean = false) {
def isClosed = !inUse
}
var resource = Resource()
// Safe の評価を含むエフェクトのスタック
type S = Fx.fx1[Safe]
def openResource: Eff[S, Resource] =
protect { resource = resource.copy(inUse = true); resource }
def closeResource(r: Resource): Eff[S, Unit] =
protect { resource = r.copy(inUse = false) }
def useResource(ok: Boolean) = (r: Resource) =>
protect[S, Int](if (ok) r.values.sum else throw new Exception("boo"))
// このプログラムは、たとえ例外があったときでも、リソースを安全に扱う
def program(ok: Boolean): (Either[Throwable, Int], List[Throwable]) =
bracket(openResource)(useResource(ok))(closeResource).
runSafe.run
> Results
Without exception: Right(55), finalizers exceptions: no exceptions, resource is closed: true
With exception : Left(boo), finalizers exceptions: no exceptions, resource is closed: true
program
のシグネチャを見れば分かるように、runSafe
が返す値は
(Either[Throwable, A], List[Throwable])
だ。1番目の部分はプログラムの結果であり、例外に終わるかもしれない。2番目の部分はファイナライザー(それ自身も失敗しうる)によって返されることがある例外のリストだ。
bracket
の単純版が
finally
だ
この例では
finally
の使い方を示しているだけでなく、ファイナライザーが失敗したときに何が起きるかも示している。
import org.atnos.eff.syntax.all._
import org.atnos.eff._, all._
// Safe の評価を含むエフェクトのスタック
type S = Fx.fx1[Safe]
var sumDone: Boolean = false
def setDone(ok: Boolean): Eff[S, Unit] =
protect[S, Unit](if (ok) sumDone = true else throw new Exception("failed!!"))
// このプログラムは計算が完了したときに sumDone を true にしようとする
def program(ok: Boolean, finalizeOk: Boolean): (Either[Throwable, Int], List[Throwable]) =
(protect[S, Int](if (ok) (1 to 10).sum else throw new Exception("boo")) `finally` setDone(finalizeOk)).
runSafe.run
> Results
Computation ok, finalizer ok: Right(55), finalizers exceptions: no exceptions
Computation ok, finalizer ko: Right(55), finalizers exceptions: List(failed!!)
Computation ko, finalizer ok: Left(boo), finalizers exceptions: no exceptions
Computation ko, finalizer ko: Left(boo), finalizers exceptions: List(failed!!)
最後に(洒落のつもりではない)、ファイナライザーの結果に興味がないときは
execSafe
が使えることを述べておく。