The Safe effect is useful to handle resources which must be closed even in the presence of exceptions. The main operations are
finally
to create an action which must always be
executed after another one, even if the first one failscatchThrowable
to handle a thrown exceptionbracket(open)(step)(close)
to open a resource, use it
and then close it safely. The close
part is a “finalizer”
Let’s see an example for the protection of a resource:
import org.atnos.eff.syntax.all._
import org.atnos.eff._, all._
// let's represent a resource which can be in use
case class Resource(values: List[Int] = (1 to 10).toList, inUse: Boolean = false) {
def isClosed = !inUse
}
var resource = Resource()
// our stack of effects, with safe evaluation
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"))
// this program uses the resource safely even if there is an exception
def program(ok: Boolean): (Throwable Either 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
As you can see in the signature of program
the return
value of runSafe
is
(Throwable Either A, List[Throwable])
. The first part is
the result of your program, which may end with an exception, and the
second part is the list of possible exceptions thrown by the finalizers
which can themselves fail.
A simpler version of bracket
is
finally
.
This example show how to use finally
but also what
happens if a finalizer fails:
import org.atnos.eff.syntax.all._
import org.atnos.eff._, all._
// our stack of effects, with safe evaluation
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!!"))
// this program tries to set sumDone to true when the computation is done
def program(ok: Boolean, finalizeOk: Boolean): (Throwable Either 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!!)
Finally (no pun intended!) note that you can use
execSafe
if you are not interested in the result of the
finalizers.