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?
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:
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With