少し前にScalaz Effects IOを使ったプログラムを書いてみましたが、今回はScala ARMを使ってみたいと思います。
まあ、IO周りのライブラリを試してるだけですが、Scala ARMの場合はfor式を使ってリソースを扱うことができるので個人的には興味を引くのですが…。
Scala ARM
http://jsuereth.com/scala-arm/index.html
Scala Automatic Resource Management(GitHub)
https://github.com/jsuereth/scala-arm
とりあえず、GitHubのページやBasic Usageなどを見て、使ってみたいと思います。
Basic Usage
http://jsuereth.com/scala-arm/usage.html
準備
依存関係の定義。Maven Central Repositoryに登録されているので、普通に設定すればいいです。
build.sbt
name := "scala-arm-example" version := "0.0.1-SNAPSHOT" scalaVersion := "2.10.3" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked") libraryDependencies ++= Seq( "com.jsuereth" %% "scala-arm" % "1.3", "org.scalatest" %% "scalatest" % "2.0" % "test" )
いくつかサンプルを
Scala ARMは、書き方として通常のものと限定継続を使ったものがあるのですが、今回は限定継続を使ったものは扱いません。
では、テストコードの雛形を。
src/test/scala/org/littlewings/arm/ScalaArmSpec.scala
package org.littlewings.arm import java.io.StringWriter import java.nio.charset.StandardCharsets import java.nio.file.{Files, NoSuchFileException, Paths} import resource._ import org.scalatest.FunSpec import org.scalatest.Matchers._ class ScalaArmSpec extends FunSpec { describe("scala-arm spec") { // ここに、テストコードを書く!! } }
ちなみに、この1行がScala ARMを使うにあたり、必要なimport文です。
import resource._
まずは、最もシンプルなパターン。
it("basic usage#1") { for (reader <- managed(Files.newBufferedReader(Paths.get("build.sbt"), StandardCharsets.UTF_8))) { Iterator .continually(reader.readLine()) .takeWhile(_ != null) .foreach(println) } }
managedメソッドにリソースを引数として与えることで、for式の中で使うことができます。また、for式(この場合foreach)を抜ける時に、リソースはクローズしてくれます。
Unitなものだけではなくて、for式でyieldを使用することで(というか、mapやflatMap)、ExtractableManagedResourceが返却されるので、ここから値を取得することができます。
it("basic usage#2") { val managedResource = for (reader <- managed(Files.newBufferedReader(Paths.get("build.sbt"), StandardCharsets.UTF_8))) yield Iterator .continually(reader.readLine()) .takeWhile(_ != null) .toList val lines = managedResource.acquireAndGet(identity) lines should have size 14 lines.head should be ("name := \"scala-arm-example\"") }
値を取り出す時は、ExtractableManagedResource#acquireAndGetで。
この場合は、acquireAndGetを呼び出したタイミングでfor式の中身が実行され、終了時にリソースはクローズされます。
2つ以上、リソースを使っても大丈夫。
it("basic usage#3") { val managedResource = for { reader <- managed(Files.newBufferedReader(Paths.get("build.sbt"), StandardCharsets.UTF_8)) writer <- managed(new StringWriter) } yield { Iterator .continually(reader.readLine()) .takeWhile(_ != null) .foreach { l => writer.write(l) writer.write(System.lineSeparator) } writer.toString } val contents = managedResource.acquireAndGet(identity) contents should startWith ("name := \"scala-arm-example\"") }
ExtractableManagedResource#acquireAndGetの代わりに、ExtractableManagedResource#acquireForを使用すると結果が直接取得できるのではなくて、Eitherになります。
it("basic usage#4") { val managedResource = for (reader <- managed(Files.newBufferedReader(Paths.get("build.sbt"), StandardCharsets.UTF_8))) yield Iterator .continually(reader.readLine()) .takeWhile(_ != null) .toList val lines = managedResource.acquireFor(identity).right.get lines should have size 14 lines.head should be ("name := \"scala-arm-example\"") }
続いて、例外発生時の挙動。
it("throw exception#1") { an [NoSuchFileException] should be thrownBy { for (reader <- managed(Files.newBufferedReader(Paths.get("not-found"), StandardCharsets.UTF_8))) { Iterator .continually(reader.readLine()) .takeWhile(_ != null) .foreach(println) } } }
foreachの場合は、その場で例外が飛んできます。リソースは、きちんとクローズされます。
ExtractableManagedResource#acquireAndGetの場合は、このメソッドを呼び出したタイミングで。
it("throw exception#2") { val managedResource = for (reader <- managed(Files.newBufferedReader(Paths.get("not-found"), StandardCharsets.UTF_8))) yield Iterator .continually(reader.readLine()) .takeWhile(_ != null) .toList an [NoSuchFileException] should be thrownBy managedResource.acquireAndGet(l => l) }
この場合も、やっぱりクローズはしてもらえるので大丈夫です。
ExtractableManagedResource#acquireForの場合はLeftになると思っていたのですが、なんか例外が飛んできましたね…。
it("throw exception#3") { val managedResource = for (reader <- managed(Files.newBufferedReader(Paths.get("not-found"), StandardCharsets.UTF_8))) yield Iterator .continually(reader.readLine()) .takeWhile(_ != null) .toList an [NoSuchFileException] should be thrownBy managedResource.acquireFor(l => l) }
そんなに悪い感じはしないんですけど、実験的なライブラリの扱いなんでしょうかね?
また、リソースとして扱えるクラスですが、デフォルトではCloseableやConnection、closeメソッドを持ったものなどが組み込まれています。resource.ResourceコンパニオンオブジェクトのScaladocを見るとよいでしょう。
これで不足する場合は、自分でResourceトレイトをMix-inしたクラスを書けばよさそうですね。
Resource Typeclass
http://jsuereth.com/scala-arm/resource.html