読者です 読者をやめる 読者になる 読者になる

Scalaz で Reader モナドと Applicative

今回は関数をモナドとして扱う Reader モナドを Scalaz で使ってみます。

  • Scalaz 7.0.0-M3

サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20121013/

sbt 用ビルドファイルは以下の通りです。

build.sbt
scalaVersion := "2.10.0-M7"

libraryDependencies += "org.scalaz" % "scalaz-core" % "7.0.0-M3" cross CrossVersion.full

Haskell の場合

まずは、Reader モナドを使った Haskell 版のサンプルです。

reader_sample.hs (Reader モナド
import Control.Monad.Instances

f1 :: Int -> Int
f1 = do
    a <- (*2)
    b <- (+10)
    return (a + b)

f2 :: Int -> Int
f2 = do
    a <- (*2)
    b <- (+10)
    c <- (+5)
    return (a + b + c)

main = do
    putStrLn $ show $ f1 4
    putStrLn $ show $ f2 4

f1 関数は引数を 1つ取り、* 2 を適用した結果を a 、+ 10 を適用した結果を b として a + b の結果を返します。
つまり、引数を x とすると以下のような計算が実施される事になります。

  • f1 関数は (2 * x) + (10 + x)
  • f2 関数は (2 * x) + (10 + x) + (5 + x)

実行結果は以下のように、f1 4 の結果が (2 * 4) + (10 + 4) = 22 、f2 4 の結果が (2 * 4) + (10 + 4) + (5 + 4) = 31 となります。

実行結果
> runghc reader_sample.hs
22
31

上記と同等の処理を Applicative で実装すると以下のようになります。

applicative_sample.hs (Applicative)
import Control.Applicative

main = do
    let f1 = (+) <$> (*2) <*> (+10)
    putStrLn $ show $ f1 4

    let f2 = (\a b c -> a + b + c) <$> (*2) <*> (+10) <*> (+5)
    putStrLn $ show $ f2 4
実行結果
> runghc applicative_sample.hs
22
31

Scalaz の場合

それでは Scalaz を使って実装してみます。

Reader モナドの場合、do が for になる以外はほぼ Haskell と同じように実装できます。

ReaderSample.scala (Reader モナド
package fits.sample

import scalaz._
import Scalaz._

object ReaderSample extends App {
    // (2 * x) + (10 + x)
    val f1 = for {
        a <- 2 * (_: Int)
        b <- 10 + (_: Int)
    } yield a + b

    println(f1(4))

    // (2 * x) + (10 + x) + (5 + x)
    val f2 = for {
        a <- 2 * (_: Int)
        b <- 10 + (_: Int)
        c <- 5 + (_: Int)
    } yield a + b + c

    println(f2(4))
}

実行結果は以下の通りです。

実行結果
scala> fits.sample.ReaderSample.main(null)
22
31


次に、Applicative での実装は以下のようになりました。

一応、Haskell の実装に近づけるよう試行錯誤してみましたが、こちらは上手くいきませんでした。(他によい方法があるかもしれませんが)

@ を使う方法をご紹介していただきましたので追加しました。こちらの方法はタプルをパターンマッチさせる必要も無く Haskell の実装に近づきました。


なお、multiply(2) <*> plus(10) 等の結果は以下のようなタプルとなるのでパターンマッチを使って処理しています。

  • multiply(2) <*> plus(10) の結果 *1 は (2 * x, 10 + x)
  • multiply(2) <*> plus(10) <*> plus(5) の結果 *2 は ( (2 * x, 10 + x), 9 + x)

ちなみに >>> は ComposeOps のメソッドで関数合成を行います。

ApplicativeSample.scala (Applicative)
package fits.sample

import scalaz._
import Scalaz._

object ApplicativeSample extends App {
    val multiply = (x: Int) => (y: Int) => x * y
    val plus = (x: Int) => (y: Int) => x + y

    val f1 = multiply(2) <*> plus(10) >>> { case (a, b) => a + b }
    println(f1(4))
    // 以下でも可
    val f1a = ^( multiply(2) <*> plus(10) ) { case (a, b) => a + b }
    println(f1a(4))
    // 以下でも可
    val f1b = ( multiply(2) |@| plus(10) ) { _ + _ }
    println(f1b(4))

    val f2 = multiply(2) <*> plus(10) <*> plus(5) >>> { case ((a, b), c) => a + b + c }
    println(f2(4))
    // 以下でも可
    val f2a = ^( multiply(2) <*> plus(10) <*> plus(5) ) { case ((a, b), c) => a + b + c }
    println(f2a(4))
    // 以下でも可
    val f2b = ( multiply(2) |@| plus(10) |@| plus(5) ) { _ + _ + _ }
    println(f2b(4))
}
実行結果
scala> fits.sample.ApplicativeSample.main(null)
22
22
22
31
31
31

最後に、ArrowOps の &&& メソッドを使って以下のように実装する事も可能です。

ArrowSample.scala (Arrow)
package fits.sample

import scalaz._
import Scalaz._

object ArrowSample extends App {
    val multiply = (x: Int) => (y: Int) => x * y
    val plus = (x: Int) => (y: Int) => x + y

    val f1 = ( multiply(2) &&& plus(10) ) >>> { case (a, b) => a + b }
    println(f1(4))

    val f2 = ( multiply(2) &&& plus(10) &&& plus(5) ) >>> { case ((a, b), c) => a + b + c }
    println(f2(4))
}
実行結果
scala> fits.sample.ArrowSample.main(null)
22
31

*1:引数が 4 の場合は (8, 14)

*2:引数が 4 の場合は ((8, 14), 9)