C # a using
avec l'interface IDisposable
. Java 7+ a une fonctionnalité identique avec try
et l'interface AutoCloseable
. Scala vous permet de choisir votre propre implémentation à ce problème.
scala-arm semble être le choix populaire et est maintenu par l’un des employés de Typesafe. Cependant, cela semble très compliqué pour un comportement aussi simple. Pour clarifier, les instructions d'utilisation sont simples, mais comprendre comment tout ce code fonctionne en interne est plutôt complexe.
Je viens d’écrire la solution super simple ARM suivante:
object SimpleARM {
def apply[T, Q](c: T {def close(): Unit})(f: (T) => Q): Q = {
try {
f(c)
} finally {
c.close()
}
}
}
Voici mon nouveau plus simple, comprendre en un coup de œil, Scala ARM. Cela prend en charge tous les cas d’utilisation auxquels je peux penser, y compris plusieurs ressources et valeurs de rendement. Ceci utilise une syntaxe d'utilisation très simple pour la compréhension:
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)
}
Voici l'utilisation de la démo:
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")
}
}
Votre approche avec un seul modèle de prêt fonctionne bien tant que vous n’avez pas besoin de travailler avec plusieurs ressources, toutes devant être gérées. Cela est permis avec l'approche monadique à bras scala.
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) => ???
}
Les fonctions principales à connaître dans scala-arm sont resource.managed
et .acquired{For,AndGet}
, ce n’est pas vraiment complexe.
C'est le code que j'utilise:
def use[A <: { def close(): Unit }, B](resource: A)(code: A ⇒ B): B =
try
code(resource)
finally
resource.close()
Contrairement aux ressources d’essai avec ressources Java, la ressource n’a pas besoin de mettre en œuvre AutoCloseable . Seule une méthode close()
est nécessaire. Elle ne prend en charge qu'une seule ressource.
Voici un exemple d'utilisation avec un InputStream
:
val path = Paths get "/etc/myfile"
use(Files.newInputStream(path)) { inputStream ⇒
val firstByte = inputStream.read()
....
}
http://illegalexception.schlichtherle.de/2012/07/19/try-with-resources-for-scala/
Une autre implémentation, probablement plus propre du point de vue "suivre les spécifications Java", mais ne parvient pas non plus à prendre en charge plusieurs ressources
celui-ci fonctionne vraiment bien pour moi:
implicit class ManagedCloseable[C <: AutoCloseable](resource: C) {
def apply[T](block: (C) => T): T = {
try {
block(resource)
} finally {
resource.close()
}
}
en l'utilisant par exemple dans ce code client Apache Cassandra:
val metadata = Cluster.builder().addContactPoint("vader").withPort(1234).build() { cluster =>
cluster.getMetadata
}
ou même plus court:
val metadata = Cluster.builder().addContactPoint("sedev01").withPort(9999).build()(_.getMetadata)
La monade paresseuse TryClose de Choppy pourrait être ce que vous recherchez. Il ressemble beaucoup à Try de Scala, mais ferme automatiquement les ressources.
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
}
Voir ici pour plus d’informations: https://github.com/choppythelumberjack/tryclose
Une amélioration que je peux recommander à l'approche que vous avez suggérée, à savoir:
def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): B = {
try
code(resource)
finally
resource.close()
}
Est à utiliser:
def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): Try[B] = {
val tryResult = Try {code(resource)}
resource.close()
tryResult
}
IMHO ayant le tryResult qui est un Try[B]
, vous permettra un flux de contrôle plus facile plus tard.