web-dev-qa-db-ja.com

Simple Scala "using / try-with-resources"(自動リソース管理)のパターン

C#にはusingインターフェイスを備えたIDisposableがあります。 Java 7+はtryおよびAutoCloseableインターフェースと同じ機能を備えています。Scalaこれにより、独自の実装を選択できます問題。

scala-armは人気のある選択肢のようで、Typesafeの従業員の1人によって保守されています。ただし、このような単純な動作では非常に複雑に見えます。明確にするために、使用手順は単純ですが、そのすべてのコードが内部でどのように機能しているかを理解することはかなり複雑です。

私はちょうど次の超単純なARMソリューションを書きました:

object SimpleARM {
  def apply[T, Q](c: T {def close(): Unit})(f: (T) => Q): Q = {
    try {
      f(c)
    } finally {
      c.close()
    }
  }
}
  • シンプルアームのようなものに何かメリットはありますか?すべての余分な複雑さは、余分な利益をもたらすはずです。
  • 通常、カスタムコードを使用するよりも、汎用的な動作のために他の人がサポートしているパブリックなオープンソースのライブラリを使用することを強くお勧めします。
  • 誰かが改善をお勧めできますか?
  • この単純なアプローチに制限はありますか?
17
clay

これが私の新しいシンプルで一目でわかるScala ARMです。これは、複数のリソースとyield値を含めることを考えることができるすべてのユースケースを完全にサポートします。これは、理解のための非常にシンプルな使用構文を使用します。

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

複数のリソースを操作する必要がなく、すべてを管理する必要がある限り、単一の単純なローンパターンを使用したアプローチは問題なく機能します。これは、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}、それほど複雑ではありません。

10
cchantep

これは私が使用するコードです:

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

Java try-with-resourcesとは異なり、リソースは実装する必要はありません AutoCloseableclose()メソッドのみが必要です。サポートするだけです。 1つのリソース。

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/

別の実装。おそらく「follow Java仕様」の観点からはよりクリーンですが、複数のリソースをサポートできません。

1
Arioch 'The

これは私にとって本当にうまくいきます:

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

あなたが提案したアプローチに対して私が推奨できる改善点は次のとおりです。

  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
  }

Try[B]であるtryResultを持つIMHOを使用すると、後で制御フローを簡単に制御できます。

0
Johnny

ChoppyのLazyTryCloseモナドはあなたが探しているものかもしれません(開示:私は著者です)。これは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

0