web-dev-qa-db-fra.com

Modèle Scala simple pour "using/try-with-resources" (gestion automatique des ressources)

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()
    }
  }
}
  • Y at-il un avantage à quelque chose comme simple-bras? Il semble que toute la complexité supplémentaire devrait offrir des avantages supplémentaires.
  • Normalement, il est hautement préférable d’utiliser une bibliothèque publique à code source ouvert, prise en charge par d’autres pour un comportement général, plutôt que d’utiliser un code personnalisé.
  • Quelqu'un peut-il recommander des améliorations?
  • Y a-t-il des limites à cette approche simple?
16
clay

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")
  }
}
4
clay

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.

11
cchantep

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()
    ....
}
3
david.perez

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

1
Arioch 'The

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)
1
Peter Ertl

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

1
ChoppyTheLumberjack

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.

0
Stas