Safe

The Safe effect is useful to handle resources which must be closed even in the presence of exceptions. The main operations are

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.