Scalaで、IOを扱う時のライブラリって、何がいいんでしょうね〜と思ったところから。
Stack Overflowには、こんなスレッドがありました。
Recommended Scala io library
http://stackoverflow.com/questions/18171104/recommended-scala-io-library
ここで紹介されていたのは、このあたり。
Scala IO
http://jesseeichar.github.io/scala-io-doc/0.4.2/index.html#!/overview
scalaz-stream
https://github.com/scalaz/scalaz-stream
Rapture I/O
http://rapture.io/
あとは、scala-armなるものもあるのだそうな。
scala-arm
https://github.com/jsuereth/scala-arm
自分は、Closeableなリソースを閉じる時はだいたいImplicit Class+Value Classで型変換していることが多かったのですが、そろそろ何かライブラリをちゃんと見た方がいいのでは?と思いまして。
で、Scala IOって止まってる?scala-armって、評判怪しい?みたいなところもありまして…。
今回は、以前にもやったScalaz Effects IOをちょっと復習してみることにしました。
Stack Overflowのスレッド関係ないですが…。
準備
今回使用するScalazは、7.1.0-M5とします。安定版ではないですが、Resourceまわりの追加機能を使いたかったもので…。
build.sbt
name := "scalaz-effect-example" version := "0.0.1-SNAPSHOT" scalaVersion := "2.10.3" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked") libraryDependencies ++= Seq( "org.scalaz" %% "scalaz-effect" % "7.1.0-M5", "org.scalatest" %% "scalatest" % "2.0" % "test" )
いくつかサンプルを
では、ScalaTestとprintlnデバッグをしながら、いくつかサンプルを書いていきます。
src/test/scala/org/littlewings/scalaz/effect/ScalazEffectIoSpec.scala
package org.littlewings.scalaz.effect import java.io.Closeable import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} import scalaz._ import scalaz.Scalaz._ import scalaz.effect._ import scalaz.std.effect.closeable._ import org.scalatest.FunSpec import org.scalatest.Matchers._ class ScalazEffectIoSpec extends FunSpec { describe("scalaz-effect io spec") { // ここに、テストを書く!! } }
とりあえず、標準入出力。
it("putStrLn, readLn") { val action: IO[Unit] = for { _ <- IO.putStrLn("Hello Scalaz-Effect IO!!") consoleIn <- IO.readLn // ここは、標準入力から1行の入力を待つ _ <- IO.putStrLn(s"Console in => ${consoleIn}") } yield () action.unsafePerformIO }
Scalaz 6の頃と、そんなに変わってないみたいです。よかったー。
続いて、Closeするコード。
以下を見ながら、マネしつつ…。
https://github.com/scalaz/scalaz/blob/scalaz-seven/tests/src/test/scala/scalaz/effect/ResourceTest.scala
it("simple closeable") { class SimpleCloseable extends Closeable { var closeCount: Int = 0 def echo(): Unit = println("Hello SimpleCloseable!") override def close(): Unit = println("Close!!") } val action = for { closeable <- IO(new SimpleCloseable) _ <- IO(closeable.echo()) _ <- Resource[Closeable].close(closeable) } yield () println("== out for") action.unsafePerformIO // ここで、 // 「new SimpleCloseable instance」、「Hello SimpleCloseable!!」、「Close!!」 // の順に表示される println("== after unsafePerfomeIO") }
ただ、この書き方だと途中で例外がスローされた場合は、Closeされないみたいです。
it("simple closeable, throw exception") { class SimpleCloseable extends Closeable { println("new SimpleClosable instance") def echo(): Unit = throw new RuntimeException("Opps!!") override def close(): Unit = println("Close!!") } val action = for { closeable <- IO(new SimpleCloseable) _ <- IO(closeable.echo()) _ <- Resource[Closeable].close(closeable) } yield () println("== out for") a [RuntimeException] should be thrownBy action.unsafePerformIO // 「Close!!」は出力されない println("== after unsafePerfomeIO") }
こういう時は、IO#usingを使えばよいのでしょうか?
it("simple closeable, throw exception#2") { class SimpleCloseable extends Closeable { println("new SimpleClosable instance#2") def echo(): Unit = throw new RuntimeException("Opps!!#2") override def close(): Unit = println("Close!!#2") } // 「import scalaz.std.effect.closeable._」としておけば、 // Closeableなものについては書かなくてもよいが、 // あえて書くなら、以下 implicit val closeAction: Resource[SimpleCloseable] = Resource.resource(Resource[Closeable].close) // Resource.resource(c => IO(c.close())) // こちらでも可 val action = IO(new SimpleCloseable).using { c => IO { c.echo() } // IOで包まないと、closeActionが実行されなかった… } println("== out for#2") a [RuntimeException] should be thrownBy action.unsafePerformIO println("== after unsafePerfomeIO#2") }
ちょこっとコメントで書いていますが、この部分
IO(new SimpleCloseable).using { c => IO { c.echo() } // IOで包まないと、closeActionが実行されなかった… }
最初はこんな感じで書いていたら、Closeされなくて考え込んでいました。
IO(new SimpleCloseable).using { c =>
c.echo()
IO.ioUnit
}
実行するアクションは、IOで包まないとダメだってことでしょうね。
なお、このusingの仕組みはIO#onExceptionで実現されてるっぽいです。
it("on exception") { val action1 = IO("Hello!") action1.unsafePerformIO should be ("Hello!") val action2 = IO(throw new RuntimeException("Oops!!")) onException (IO.putStrLn("Got!! exception")) a [RuntimeException] should be thrownBy action2.unsafePerformIO }
あとはファイル入出力の例。
it("open file") { implicit val closeAction: Resource[scala.io.BufferedSource] = Resource.resource { source => IO(source.close()) } val action = IO(scala.io.Source.fromFile("build.sbt")).using { source => IO(source.getLines().toList) } action.unsafePerformIO.head should be ("name := \"scalaz-effect-example\"") } it("open file2") { def readLines(reader: java.io.BufferedReader): Unit = Iterator .continually(reader.readLine) .takeWhile(_ != null) .foreach(println) val action = IO(Files.newBufferedReader(Paths.get("build.sbt"), StandardCharsets.UTF_8)).using { reader => IO(readLines(reader)) } action.unsafePerformIO // build.sbtの中身が表示される }
とりあえず動かすことはできましたけど、ScalaのIOライブラリって何が標準なんでしょうねぇ…。
ローンパターンを適用して書けるのはもちろんなのですが、リソースクローズをやってくれつつfor式で書けるものって??できれば、複数リソースを使ってもネストせずに書きたいのですよねぇ。一応、今度scala-armも試してみるかなぁ。
あと、最近Scalaz自体にまた興味が出てきたので、少しずつ見ていこうかと思っています。