Haskell と Scalaz でモナドを自作
今回は、Haskell と Scalaz でモナドを自作してみました。
良い題材を思いつかなかったので、とりあえず以下のような単純なモナド(Counter モナドとする)を自作する事にしました。
Haskell・Scalaz のどちらも、概ね以下のようにしてモナドを作成します。
なお、Monad のインスタンスを定義する際、以下の関数(もしくはメソッド)を実装する事になります。
種類 | Haskell | Scalaz |
---|---|---|
注入関数 | return | point |
連鎖関数 | >>= | bind |
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20121111/
ちなみに、このブログの最初のサンプルはモナド則を満たしていなかった *3 ので修正しました。
Haskell の場合
それでは、Haskell で Counter モナドを作成してみます。
まず、モナドとする型 Counter を定義します。任意の型の値と Int 型のカウンターをタプルとしてフィールドに持つ型を newtype で定義し、getCount 関数でタプルの内容を取得できるようにしています。(1)
次に、Counter を Monad のインスタンスとして定義し、return と >>= 関数を以下のように実装しています。(2)
- 注入関数 return は引数 x の値とカウンター値が 0 のタプルを持つ Counter モナドを返す (a)
- 連鎖関数 >>= は元の Counter モナドが持つ値 x にモナディック関数 f を適用した結果のモナドから取り出した値 y とカウンター d に元の Counter モナドが持つカウンター c を加算したタプルを持つ Counter モナドを返す (b)
最後に、Counter モナドの動作を確認するため、モナディック関数 append を定義し main で return や >>= を試しています。
append は Counter モナドの値 x に指定の文字列 s を連結しカウンターが 1 の Counter モナドを返します。
counter.hs (Haskell版 Counter モナド)
-- (1) モナドとして扱う型を定義 newtype Counter a = Counter { getCount :: (a, Int) } -- (2) Monad のインスタンスを定義 instance Monad Counter where -- (a) 注入関数の実装 return x = Counter (x, 0) -- (b) 連鎖関数の実装 (Counter (x, c)) >>= f = let (y, d) = getCount(f x) in Counter (y, c + d) -- モナディック関数 append :: String -> String -> Counter String append s x = Counter (x ++ s, 1) -- Counter モナドの利用 main = do -- ("a", 0) print $ getCount $ return "a" -- ("ab", 1) 左恒等性 print $ getCount $ return "a" >>= append "b" print $ getCount $ append "b" "a" -- ("abc", 2) print $ getCount $ return "a" >>= append "b" >>= append "c" -- ("d", 3) 右恒等性 print $ getCount $ Counter ("d", 3) >>= return
実行結果は以下の通り。>>= で append を繋ぐ度にカウンターが増えている事を確認できます。
実行結果
> runghc counter.hs ("a",0) ("ab",1) ("ab",1) ("abc",2) ("d",3)
Scalaz の場合
今度は、Scalaz で Counter モナドを作成してみます。
まず、モナドとする型 Counter を定義します。任意の値とカウンターのタプルを持つ case class を定義し、count でタプルの内容を取得できるようにしています。(1)
次に、以下のようにして Counter を Monad のインスタンスとしています。(2)
- CounterInstances トレイトを定義
- CounterInstances トレイト内に counterInstance を定義し Monad トレイトの実装を設定
- CounterInstances トレイトを extends するコンパニオンオブジェクトを定義
CounterSample.scala (Scalaz版 Counter モナド)
package fits.sample import scalaz._ import Scalaz._ // (1) モナドとして扱う型を定義 case class Counter[A](count: (A, Int)) // (2) Monad のインスタンスを定義 trait CounterInstances { implicit val counterInstance = new Monad[Counter] { // (a) 注入関数の実装 def point[A](x: => A): Counter[A] = Counter (x, 0) /** * (b) 連鎖関数の実装 * * b1. Counter モナド fa からタプルの内容を取得 * b2. x の値に f を適用した結果の Counter モナドからタプルの内容を取得 * b3. y の値とカウンター値 d に fa のカウンター値 c を加算した値を持つ Counter モナドを返す */ def bind[A, B](fa: Counter[A])(f: (A) => Counter[B]): Counter[B] = { val (x, c) = fa.count // b1. val (y, d) = f(x).count // b2. Counter (y, c + d) // b3. } } } // (2) Monad のインスタンスを定義 case object Counter extends CounterInstances // Counter モナドの利用 object CounterSample extends App { import Counter.counterInstance.point // モナディック関数 val append = (s: String) => (x: String) => Counter (x + s, 1) // ("a", 0) point("a").count |> println // ("ab", 1) 左恒等性 ( point("a") >>= append("b") ).count |> println ( append("b")("a") ).count |> println // ("abc", 2) ( point("a") >>= append("b") >>= append("c") ).count |> println // (d, 3) 右恒等性 ( Counter ("d", 3) >>= { s => point(s) } ).count |> println }
sbt 用ビルドファイルの内容は以下の通りです。
build.sbt
scalaVersion := "2.10.0" libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.0.0-M7" mainClass in (Compile, run) := Some("fits.sample.CounterSample")
sbt による実行結果は以下の通りです。
実行結果
> sbt run ・・・ [info] Running fits.sample.CounterSample (a,0) (ab,1) (ab,1) (abc,2) (d,3)