Reputation: 20460
C# has using
with the IDisposable
interface. Java 7+ has identical functionality with try
and the AutoCloseable
interface. Scala lets you choose your own implementation to this issue.
scala-arm seems to be the popular choice, and is maintained by one of the Typesafe employees. However, it seems very complicated for such a simple behavior. To clarify, the usage instructions are simple, but understanding how all that code is working internally is rather complex.
I just wrote the following super simple ARM solution:
object SimpleARM {
def apply[T, Q](c: T {def close(): Unit})(f: (T) => Q): Q = {
try {
f(c)
} finally {
c.close()
}
}
}
Upvotes: 20
Views: 17554
Reputation: 1445
Here's one way of doing this in Scala <= 2.12
trait SourceUtils {
/**
* Opens a `resource`, passes it to the given function `f`, then
* closes the resource, returning the value returned from `f`.
*
* Example usage:
* {{{
* val myString: String =
* using(scala.io.Source.fromFile("file.name")) { source =>
* source.getLines.mkString
* }
* }}}
*
* @param resource closeable resource to use, then close
* @param f function which maps the resource to some return value
* @tparam T type of the resource to be opened / closed
* @tparam U type of the return value
* @return the result of the function `f`
*/
def using[T <: AutoCloseable, U](resource: => T)(f: T => U): U = {
scala.util.Try(resource).fold(throw _, source => {
val result = f(source)
source.close()
result
})
}
}
Upvotes: 0
Reputation: 1802
This is how I'd do it:
def tryFinally[A, B](acquire: Try[A])(use: A => B)(release: A => Unit): Try[B] =
for {
resource <- acquire
r = Try(use(resource)).fold(
e => { release(resource); throw e },
b => { release(resource); b }
)
} yield r
you can call .get
on it and make it return B
.
Upvotes: 0
Reputation: 777
Choppy's Lazy TryClose monad might be what you are looking for (disclosure: I'm the author). It is very similar to Scala's Try but automatically closes resources automatically.
val ds = new JdbcDataSource()
val output = for {
conn <- TryClose(ds.getConnection())
ps <- TryClose(conn.prepareStatement("select * from MyTable"))
rs <- TryClose.wrap(ps.executeQuery())
} yield wrap(extractResult(rs))
// Note that Nothing will actually be done until 'resolve' is called
output.resolve match {
case Success(result) => // Do something
case Failure(e) => // Handle Stuff
}
See here for more info: https://github.com/choppythelumberjack/tryclose
Upvotes: 0
Reputation: 15423
An improvement I can recommend to the approach you suggested, which is:
def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): B = {
try
code(resource)
finally
resource.close()
}
Is to use:
def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): Try[B] = {
val tryResult = Try {code(resource)}
resource.close()
tryResult
}
IMHO having the tryResult which is an Try[B]
, will allow you an easier control flow later.
Upvotes: 0
Reputation: 249
this one works for me really well:
implicit class ManagedCloseable[C <: AutoCloseable](resource: C) {
def apply[T](block: (C) => T): T = {
try {
block(resource)
} finally {
resource.close()
}
}
using it for example in this Apache Cassandra client code:
val metadata = Cluster.builder().addContactPoint("vader").withPort(1234).build() { cluster =>
cluster.getMetadata
}
or even shorter:
val metadata = Cluster.builder().addContactPoint("sedev01").withPort(9999).build()(_.getMetadata)
Upvotes: 1
Reputation: 7032
This is the code I use:
def use[A <: { def close(): Unit }, B](resource: A)(code: A ⇒ B): B =
try
code(resource)
finally
resource.close()
Unlike Java try-with-resources, the resource doesn't need to implement AutoCloseable. Only a close()
method is needed.
It only supports one resource.
Here is an example use with an InputStream
:
val path = Paths get "/etc/myfile"
use(Files.newInputStream(path)) { inputStream ⇒
val firstByte = inputStream.read()
....
}
Upvotes: 3
Reputation: 20460
Here is my newer simple, understand at a glance, Scala ARM. This fully supports every use case I can think of including multiple resources and yield values. This uses a very simple for comprehension usage syntax:
class AutoCloseableWrapper[A <: AutoCloseable](protected val c: A) {
def map[B](f: (A) => B): B = {
try {
f(c)
} finally {
c.close()
}
}
def foreach(f: (A) => Unit): Unit = map(f)
// Not a proper flatMap.
def flatMap[B](f: (A) => B): B = map(f)
// Hack :)
def withFilter(f: (A) => Boolean) = this
}
object Arm {
def apply[A <: AutoCloseable](c: A) = new AutoCloseableWrapper(c)
}
Here's demo use:
class DemoCloseable(val s: String) extends AutoCloseable {
var closed = false
println(s"DemoCloseable create ${s}")
override def close(): Unit = {
println(s"DemoCloseable close ${s} previously closed=${closed}")
closed = true
}
}
object DemoCloseable {
def unapply(dc: DemoCloseable): Option[(String)] = Some(dc.s)
}
object Demo {
def main(args: Array[String]): Unit = {
for (v <- Arm(new DemoCloseable("abc"))) {
println(s"Using closeable ${v.s}")
}
for (a <- Arm(new DemoCloseable("a123"));
b <- Arm(new DemoCloseable("b123"));
c <- Arm(new DemoCloseable("c123"))) {
println(s"Using multiple resources for comprehension. a.s=${a.s}. b.s=${b.s}. c.s=${c.s}")
}
val yieldInt = for (v <- Arm(new DemoCloseable("abc"))) yield 123
println(s"yieldInt = $yieldInt")
val yieldString = for (DemoCloseable(s) <- Arm(new DemoCloseable("abc")); c <- s) yield c
println(s"yieldString = $yieldString")
println("done")
}
}
Upvotes: 6
Reputation: 16065
http://illegalexception.schlichtherle.de/2012/07/19/try-with-resources-for-scala/
Another implementation, probably more clean from "follow Java specifications" viewpoint, but also fails to support multiple resources
Upvotes: 1
Reputation: 9168
Your approach with a single simple loan pattern is working fine as long as you don't need to work with several resources, all needing to be managed. That's allowed with scala-arm monadic approach.
import resource.managed
managed(openResA).and(managed(openResB)) acquireFor { (a, b) => ??? }
val res = for {
a <- managed(openResA)
b <- managed(openResB)
c <- managed(openResC)
} yield (a, b, c)
res acquireAndGet {
case (a, b, c) => ???
}
Main functions to know in scala-arm is resource.managed
and .acquired{For,AndGet}
, not really complex btw.
Upvotes: 10