CLOVER🍀

That was when it all began.

Scalazを始めてみました

最近、日本のScala界隈で名前をよく目にするもので、Scalazというライブラリがあります。

Scalaz
http://code.google.com/p/scalaz/

Scala標準ライブラリとJ2SEにのみ依存しているライブラリで、Scalaの標準ライブラリを補うことを目的に作られているっぽいですね。

日本語でのまとまった情報なんかはあまり見つかりませんが、slideshareで参考になりそうなものを見つけました。
http://www.slideshare.net/kmizushima/scalaz
http://www.slideshare.net/SanshiroYoshida/scalaz-effects

2つ目のslideshareの続き
http://basking-cat.blogspot.com/2011/09/scalaziteratee.html
http://basking-cat.blogspot.com/2011/09/scalaziteratee_06.html

資料を見た時は「ふ〜ん」って感じでしたが、ちょこっと使ってみるとすごく学習曲線が高そうな印象を受けます…。というか、まだ意味がわかりません。なんとなく、使う際にはHaskellの知識があると少しハードルが下がりそうな気がしますけど。

まあ、とりあえず最初の一歩ということで。

インストール

sbtを使っているなら、以下の1行をbuild.sbtに加えるだけ。

libraryDependencies += "org.scalaz" %% "scalaz-core" % "6.0.3"

Mavenでも利用可能です。普通に使う場合は、JARをダウンロードしてきてクラスパスが通っている場所に放り込めばいいと思います。

事前準備

これは非常に簡単です。Scalazを使用するScalaコード中に、以下の2行を加えるだけです。

import scalaz._
import Scalaz._

個人的には、こちらの書き方の方が好み…。

import scalaz._
import scalaz.Scalaz._

んで、同梱されているサンプルコードを見ながら軽くプログラミング。

    "123".parseInt.foreach(i => println("123 parseInt => " + i))
    "abc".parseInt.foreach(i => println("abc parseInt => " + i))

StringにparseIntなんてメソッドなんてありませんが、暗黙の型変換(implicit conversion)を使って変換後に呼び出しているようです。

実行すると、こうなります。

123 parseInt => 123

「abc」で、StringにparseIntをかけた場合の結果が現れていません。ここには、parseIntの戻り値であるValidationトレイトが作用しているようです。

ScalazはScalaの基本的な型(Int、String、List、Optionなどなど)に対して、implicit conversion経由でいろいろと便利なメソッドを提供しているらしいです。

上記slideshareからの、implicit conversionが絡んだ型の命名規則を抜粋すると、以下の様になっているそうな。

  • implicit conversionを提供するトレイトは、末尾が「s」となる(ex. Strings)
  • implicit conversionによる変換後の型は、末尾が「W」となる(ex. StringW)

とにかくimplicit conversionが使われ過ぎていて、何が起こっているのか全然分からないのでしばらくコンパイル時にimplicit conversionの結果を出力するようにしました。
build.sbtに以下を定義。

scalacOptions += "-Xprint:typer"

必要であれば、「reload」とタイプすることをお忘れなきよう。

先ほどのparseIntを使ったサンプルコードは、以下の様に展開されます。

      scalaz.Scalaz.StringTo("123").parseInt.foreach[Unit](((i: Int) => scala.this.Predef.println("123 parseInt => ".+(i))));
      scalaz.Scalaz.StringTo("abc").parseInt.foreach[Unit](((i: Int) => scala.this.Predef.println("abc parseInt => ".+(i))));

次、畳み込みの例。まだfoldMapとfoldMapDefaultの違いが分かっていません…。

    println("List(100, 200, 300) foldMap => " + List(100, 200, 300).foldMap(x => x))
    println("List(100, 200, 300) foldMapDefault => " + List(100, 200, 300).foldMapDefault(x => x))

    println("""List("abc", "def", "ghi") foldMap => """ + List("abc", "def", "ghi").foldMap(x => x))
    println("""List("abc", "def", "ghi") foldMapDefault => """ + List("abc", "def", "ghi").foldMapDefault(x => x))

結果。

List(100, 200, 300) foldMap => 600
List(100, 200, 300) foldMapDefault => 600
List("abc", "def", "ghi") foldMap => abcdefghi
List("abc", "def", "ghi") foldMapDefault => abcdefghi

implicit conversionをトレースすると、こんなことに…。

      scala.this.Predef.println("List(100, 200, 300) foldMap => ".+(scalaz.Scalaz.SeqMA[List, Int](immutable.this.List.apply[Int](100, 200, 300)).foldMap[Int](((x: Int) => x))(scalaz.this.Foldable.ListFoldable, scalaz.this.Monoid.monoid[Int](scalaz.this.Semigroup.IntSemigroup, scalaz.this.Zero.IntZero))));
      scala.this.Predef.println("List(100, 200, 300) foldMapDefault => ".+(scalaz.Scalaz.SeqMA[List, Int](immutable.this.List.apply[Int](100, 200, 300)).foldMapDefault[Int](((x: Int) => x))(scalaz.this.Traverse.TraversableTraverse[List](scalaz.this.CanBuildAnySelf.GenericCanBuildSelf[List](immutable.this.List.canBuildFrom[Nothing])), scalaz.this.Monoid.monoid[Int](scalaz.this.Semigroup.IntSemigroup, scalaz.this.Zero.IntZero))));
      scala.this.Predef.println("List(\"abc\", \"def\", \"ghi\") foldMap => ".+(scalaz.Scalaz.SeqMA[List, java.lang.String](immutable.this.List.apply[java.lang.String]("abc", "def", "ghi")).foldMap[java.lang.String](((x: java.lang.String) => x))(scalaz.this.Foldable.ListFoldable, scalaz.this.Monoid.monoid[java.lang.String](scalaz.this.Semigroup.StringSemigroup, scalaz.this.Zero.StringZero))));
      scala.this.Predef.println("List(\"abc\", \"def\", \"ghi\") foldMapDefault => ".+(scalaz.Scalaz.SeqMA[List, java.lang.String](immutable.this.List.apply[java.lang.String]("abc", "def", "ghi")).foldMapDefault[java.lang.String](((x: java.lang.String) => x))(scalaz.this.Traverse.TraversableTraverse[List](scalaz.this.CanBuildAnySelf.GenericCanBuildSelf[List](immutable.this.List.canBuildFrom[Nothing])), scalaz.this.Monoid.monoid[java.lang.String](scalaz.this.Semigroup.StringSemigroup, scalaz.this.Zero.StringZero))));

なが〜い。

あんまり情報もないので、かなりてこずりそうなのですがちょっと面白そうなので、Gaucheの勉強と並行で頑張ってみようかと思います。

とりあえず、今日書いてみたコード。ほとんどサンプルから切り取ってきたものです。

import scalaz._
import scalaz.Scalaz._

object ScalazExample {
  def main(args: Array[String]): Unit = {
    println(List(some(7), some(9)).sequence)

    "123".parseInt.foreach(i => println("123 parseInt => " + i))
    "abc".parseInt.foreach(i => println("abc parseInt => " + i))

    println("List(100, 200, 300) foldMap => " + List(100, 200, 300).foldMap(x => x))
    println("List(100, 200, 300) foldMapDefault => " + List(100, 200, 300).foldMapDefault(x => x))

    println("""List("abc", "def", "ghi") foldMap => """ + List("abc", "def", "ghi").foldMap(x => x))
    println("""List("abc", "def", "ghi") foldMapDefault => """ + List("abc", "def", "ghi").foldMapDefault(x => x))

    println("List(100, 200, 300) collapse => " + List(100, 200, 300).collapse)
    println("""List("abc", "def", "ghi") collapse => """ +List("abc", "def", "ghi").collapse)

    println("List(100, 200, 300) foldMap ∏ => " + List(100, 200, 300).foldMap(x => x ∏))
    println("List(100, 200, 300) foldMap ∏ => " + List(100, 200, 300).foldMapDefault(x => x ∏))


    def g(s: String): FirstOption[Int] = {
      val validation = s.parseInt
      val option = validation.either.right.toOption
      val fst = option.fst
      fst
    }
    
    println("""(List("abc", "def") ↦ g).value => """ + (List("abc", "def") ↦ g).value)
    println("(List(7, 8) ↦ g).value => " + (List("7", "8") ↦ g).value)
  }
}

実行結果。

Some(List(7, 9))
123 parseInt => 123
List(100, 200, 300) foldMap => 600
List(100, 200, 300) foldMapDefault => 600
List("abc", "def", "ghi") foldMap => abcdefghi
List("abc", "def", "ghi") foldMapDefault => abcdefghi
List(100, 200, 300) collapse => 600
List("abc", "def", "ghi") collapse => abcdefghi
List(100, 200, 300) foldMap ∏ => 6000000
List(100, 200, 300) foldMap ∏ => 6000000
(List("abc", "def") ↦ g).value => None
(List(7, 8) ↦ g).value => Some(List(7, 8))

なんかメソッド名に「↦」とか「∏」とかの記号が入っていて、超面食らうのですけど…。メソッド名にUnicode文字が使われているわけですが、作った方々って、これをどうやって入力しているんでしょうか…。