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

Haskell と Scalaz でモナドを自作

今回は、Haskell と Scalaz でモナドを自作してみました。

良い題材を思いつかなかったので、とりあえず以下のような単純なモナド(Counter モナドとする)を自作する事にしました。

  • カウンターを持たせて、バインドで処理を繋ぐ度にカウンター同士を加算するモナド *1

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)

*1:通常、このような用途には Writer モナドを使います

*2:HaskellMonad 型クラス、Scalaz は Monad トレイト

*3:つまり、モナドでは無かったという事です