CLOVER🍀

That was when it all began.

ScalaでIOを扱うには…から、Scalaz Effects IO

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自体にまた興味が出てきたので、少しずつ見ていこうかと思っています。