问题
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()
}
}
}
- Is there any benefit to something like simple-arm? It seems all the extra complexity should deliver extra benefit.
- Normally, it is highly preferable to use a public, open source, library that is supported by others for general purpose behavior over using custom code.
- Can anyone recommend any improvements?
- Are there any limitations to this simple approach?
回答1:
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.
回答2:
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")
}
}
回答3:
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()
....
}
回答4:
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
回答5:
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)
回答6:
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.
回答7:
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
来源:https://stackoverflow.com/questions/25634455/simple-scala-pattern-for-using-try-with-resources-automatic-resource-manageme