Safe

Safe エフェクトはたとえ例外が発生したときでもクローズされなければいけないリソースを扱うときに便利だ。主な操作は、

リソースの保護の例を見てみよう。

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 が使えることを述べておく。