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