Asger Eir
Asger Eir

Reputation: 41

How to ensure a resource is closed in a for-comprehension in Scala

How is functions with side-effects best handled in for-comprehensions in Scala?

I have a for comprehension that starts by creating a kind of resource (x) by calling a function f1. This resource has a close-method that needs to be called at the end but also if the for-comprehension fails somehow (unless.

So we have something like:

import scala.util.{Try,Success,Failure}

trait Resource {
  def close() : Unit
}

// Opens some resource and returns it as Success or returns Failure
def f1 : Try[Resource] = ...
def f2 : Try[Resource] = ...

val res = for {
  x <- f1
  y <- f2
} yield {
  (x,y)
}

Where should I call the close method? I can call it at the end of the for-comprehension as the last statement (z <- x.close), in the yield-part, or after the for-comprehension (res._1.close). None of them ensures that close is called if an error occurs (e.g. if f2 fails). Alternatively, I could separate

x <- f1 

out of the for-comprehension like this:

val res = f1
res match {
  case Success(x) => {
    for {
      y <- f2
    }
    x.close
  }

  case Failure(e) => ...       
:

That would ensure the call of close but is not very nice code. Is there not a smarter and more clean way to achieve the same?

Upvotes: 4

Views: 1806

Answers (3)

Sascha Kolberg
Sascha Kolberg

Reputation: 7162

A common pattern for closing resources is the loan pattern:

type Closable = { def close(): Unit }

def withClosable[B](closable: Closable)(op: Closable => B): B = {
  try {
    op(closable)
  } finally {
    closable.close()
  }
}

With a little refactoring you can use this pattern:

import scala.util.{Try,Success,Failure}

trait Resource {
  def close() : Unit
}

// Opens some resource and returns it as Success or returns Failure
def f1(res: Resource) : Try[Resource] = ???
def f2(res: Resource) : Try[Resource] = ???

val f1Resource: Resource = ???
val f2Resource: Resource = ???

val res = for {
  x <- withClosable(f1Resource)(f1)
  y <- withClosable(f2Resource)(f2)
} yield {
  (x,y)
}

or

import scala.util.{Try,Success,Failure}

trait Resource {
  def close() : Unit
}

// Opens some resource and returns it as Success or returns Failure
def f1: Try[Resource] = { 
  val res: Resource = ???
  withClosable(res){ ... }
}
def f2: Try[Resource] = { 
  val res: Resource = ???
  withClosable(res){ ... }
}

val res = for {
  x <- f1
  y <- f2
} yield {
  (x,y)
}

Upvotes: 3

Oleg Rudenko
Oleg Rudenko

Reputation: 698

When I have such problem I decide between 2 possibilities:

  1. Use Scala ARM
  2. Implement Loan Pattern on my own (link is volatile and could die)

In most cases I prefer own implementation to avoid additional dependency. Here is the code of Loan Pattern:

def using[A](r : Resource)(f : Resource => A) : A =
    try {
        f(r)
    } finally {
        r.close()
    }

Usage:

using(getResource())(r =>
    useResource(r)
)

Since you need 2 resources you will need to use this pattern twice:

using(getResource1())(r1 =>
    using(getResource2())(r2 =>
        doYourWork(r1, r2)))

You can also look on following answers:

Upvotes: 8

Tyth
Tyth

Reputation: 1824

You could use

https://github.com/jsuereth/scala-arm

If your "resource" does not implement java.io.Closeable (or some other closable interface, supported by than library) you just need to write an implicit conversion:

implicit def yourEnititySupport[A <: your.closable.Enitity]: Resource[A] = 
  new Resource[A] {
    override def close(r: A) = r.commit()
    // if you need custom behavior here
    override def closeAfterException(r: A, t: Throwable) = r.rollback()
  }

And use it like this:

import resource._

for {
  a <- managed(your.closable.Enitity())
  b <- managed(your.closable.Enitity())
} { doSomething(a, b) }

Upvotes: 1

Related Questions