Reputation: 41
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
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
Reputation: 698
When I have such problem I decide between 2 possibilities:
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
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