ScalazのValidationでFizzBuzz

Scalaz使ってみたかったのでScalaインストールした。
Scala素人なのでコードおかしいかも。

import scalaz._
import Scalaz._

object FizzBuzz {
    def makeValidator(d:Int, msg:String) = {
        (n:Int) =>
            if (n % d != 0)
                n.success
            else
                nel(msg).fail
    }
    val fizz = makeValidator(3, "Fizz")
    val buzz = makeValidator(5, "Buzz")
    def fizzBuzz(n:Int) {
        (fizz(n) |@| buzz(n)) { (_,_) => n } match {
            case Success(p) => println(p)
            case Failure(e) => println(e.list mkString "" )
        }
    }
    def main(args: Array[String]) {
        for (n <- 1 to 100)
            fizzBuzz(n)
    }
}

Scala の Either についての考察 - xuwei-k's blog」で「fail の場合の値を NonEmptyList として accumulate することができる」という話を見て思いついた。
3(or 5)で割り切れたら入力エラーとしてエラー文言"Fizz"(or "Buzz")をaccumulateしていくイメージ。
{ (_,_) => n }がなんとも言えずダサい感じ。

おまけ: ScalazのWriterでFizzBuzz

JavaでWriterモナドでFizzBuzz - terazzoの日記」の焼き直し。

import scalaz._
import Scalaz._

object FizzBuzz {
    type Log[A] = Writer[List[String], A]
    implicit def LogMA[A](l: Log[A]): MA[Log, A] = ma[Log, A](l)

    def makeOps(d:Int, msg:String) = {
        (n:Int) =>
            if (n % d != 0)
                n.pure[Log]
            else
                n.set(List(msg))
    }
    val fizz = makeOps(3, "Fizz")
    val buzz = makeOps(5, "Buzz")
    def fizzBuzz(n:Int) {
        // 「n fizz buzz value」に近い感じで、通常より直感的に表現!
        n.pure[Log] >>= fizz >>= buzz value match {
            case (Nil, over)  => println(over)
            case (written, _) => println(written mkString "")
        }
    }

    def main(args: Array[String]) {
        for (n <- 1 to 100)
            fizzBuzz(n)
    }
}

Validationはそれ自体がApplicativeだけど、Writerはログの中身だけがSemigroupになっているのが違う(たぶん)
あんまり良く分かってるわけではないので適当に聞き流してください。

蛇足: StringもSemigroup/Monoid扱いできた

Listにする必要なかった。

import scalaz._
import Scalaz._

object FizzBuzzByValidation {
    def makeValidator(d:Int, msg:String) = {
        (n:Int) =>
            if (n % d != 0)
                n.success
            else
                msg.fail
    }
    val fizz = makeValidator(3, "Fizz")
    val buzz = makeValidator(5, "Buzz")
    def fizzBuzz(n:Int) {
        (fizz(n) |@| buzz(n)) { (_,_) => n } match {
            case Success(p) => println(p)
            case Failure(e) => println(e)
        }
    }
    def main(args: Array[String]) {
        for (n <- 1 to 100)
            fizzBuzz(n)
    }
}
object FizzBuzzByWriter {
    type Log[A] = Writer[String, A]
    implicit def LogMA[A](l: Log[A]): MA[Log, A] = ma[Log, A](l)

    def makeOps(d:Int, msg:String) = {
        (n:Int) =>
            if (n % d != 0)
                n.pure[Log]
            else
                n.set(msg)
    }
    val fizz = makeOps(3, "Fizz")
    val buzz = makeOps(5, "Buzz")
    def fizzBuzz(n:Int) {
        // 「n fizz buzz value」に近い感じで、通常より直感的に表現!
        n.pure[Log] >>= fizz >>= buzz value match {
            case (s, over) if s == mzero[String] => println(over)
            case (written, _)                    => println(written)
        }
    }

    def main(args: Array[String]) {
        for (n <- 1 to 100)
            fizzBuzz(n)
    }
}

蛇足2: flatMapを「>>=」に変更した

上の例の「implicit def LogMA[A](l: Log[A]): MA[Log, A] = ma[Log, A](l)」の行が何をしているかというと、「>>=」が使えるようにLog[A]からMA[Log, A]への変換を定義している。この行がないと、以下のようなエラーが出る。

[info] Compiling 1 Scala source to C:\projects\ScalazSample\target\scala-2.9.1\classes...
[error] C:\projects\ScalazSample\src\main\scala\FizzBuzz.scala:18: reassignment to val
[error]         n.pure[Log] >>= fizz >>= buzz value match {
[error]                     ^
[error] one error found

「>>=」が上手く解釈できずに、挙句に再代入と思ってしまっているらしい。
ぐぐって見つけたページによるとReaderのようなパラメータ複数のやつをMAに変換する際に起こるらしく、MAへの変換を明示的に(といってもimplicit defだけど)追加してやると解釈できるようになるそうだ。


MABの方に変換するのが筋のような気もするけど……(こちらでも動く)

    implicit def WriterMAB[W, A](w: Writer[W, A]): MAB[Writer, W, A] = mab[Writer, W, A](w)

あ、Scalazのバージョンは6.0.3です。