Простой шаблон Scala для использования /try -with-resources (Автоматическое управление ресурсами)

С# имеет using с интерфейсом IDisposable. Java 7+ имеет идентичные функциональные возможности с интерфейсом try и AutoCloseable. Scala позволяет вам выбрать свою реализацию для этой проблемы.

scala -arm, по-видимому, является популярным выбором и поддерживается одним из сотрудников Typeafe. Однако для такого простого поведения это кажется очень сложным. Чтобы уточнить, инструкции по использованию просты, но понимание того, как весь этот код работает внутри, довольно сложный.

Я просто написал следующее супер простое решение ARM:

object SimpleARM {
  def apply[T, Q](c: T {def close(): Unit})(f: (T) => Q): Q = {
    try {
      f(c)
    } finally {
      c.close()
    }
  }
}
  • Есть ли какая-нибудь польза для чего-то вроде простой руки? Кажется, вся дополнительная сложность должна принести дополнительную пользу.
  • Обычно очень предпочтительно использовать общедоступную библиотеку с открытым исходным кодом, которая поддерживается другими для поведения общего назначения с использованием настраиваемого кода.
  • Кто-нибудь может рекомендовать какие-либо улучшения?
  • Существуют ли какие-либо ограничения для этого простого подхода?

Ответы

Ответ 1

Вот мой новый, простой, понятный, Scala ARM. Это полностью поддерживает каждый случай использования, я могу думать о включении нескольких ресурсов и значений доходности. Это использует очень простой для использования синтаксис использования:

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)
}

Здесь используется демо:

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

Ответ 2

Ваш подход с одним простым шаблоном займа работает нормально, если вам не нужно работать с несколькими ресурсами, и все они нуждаются в управлении. Это разрешено с помощью scala -arm монадического подхода.

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) => ???
}

Основные функции, которые нужно знать в scala -arm, - это resource.managed и .acquired{For,AndGet}, не очень сложный btw.

Ответ 3

Это код, который я использую:

def use[A <: { def close(): Unit }, B](resource: A)(code: A ⇒ B): B =
    try
        code(resource)
    finally
        resource.close()

В отличие от Java try-with-resources, ресурсу не нужно реализовывать AutoCloseable. Требуется только метод close(). Он поддерживает только один ресурс.

Вот пример использования с InputStream:

val path = Paths get "/etc/myfile"
use(Files.newInputStream(path)) { inputStream ⇒
    val firstByte = inputStream.read()
    ....
}

Ответ 5

этот работает для меня очень хорошо:

  implicit class ManagedCloseable[C <: AutoCloseable](resource: C) {
    def apply[T](block: (C) => T): T = {
    try {
      block(resource)
    } finally {
      resource.close()
    }
  }

используя его, например, в этом клиентском коде Apache Cassandra:

val metadata = Cluster.builder().addContactPoint("vader").withPort(1234).build() { cluster =>
  cluster.getMetadata
}

или даже короче:

val metadata = Cluster.builder().addContactPoint("sedev01").withPort(9999).build()(_.getMetadata)

Ответ 6

Улучшение, которое я могу рекомендовать для предложенного вами подхода:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): B = {
    try
      code(resource)
    finally
      resource.close()
  }

Использовать:

  def autoClose[A <: AutoCloseable, B](resource: A)(code: A ⇒ B): Try[B] = {
    val tryResult = Try {code(resource)}
    resource.close()
    tryResult
  }

IMHO, имеющий tryResult, который является Try[B], позволит вам более легкий поток управления позже.

Ответ 7

Choppy Lazy TryClose monad может быть тем, что вы ищете. Он очень похож на Scala Try, но автоматически автоматически закрывает ресурсы.

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
}

См. здесь для получения дополнительной информации: https://github.com/choppythelumberjack/tryclose