State モナドの作り方で分かるように、モナドの作り方は非常にシンプルだ。代数的データ型をつくり、その型を Monad タイプクラスのインスタンスにして。return 関数と >>= 演算子の実装をするだけだからだ。
非常に単純なデータ型の場合は次のようになるし、 Prelude> :set +m Prelude> newtype M a = M a deriving Show Prelude> instance Monad M where Prelude| return x = M x Prelude| (M x) >>= f = f x Prelude| Prelude> M 2 >>= \x -> M (x*2) M 4 State モナドの場合は、以下のようになる。 Prelude> newtype State s a = State { runState :: s -> (a,s) } Prelude> instance Monad (State s) where Prelude| return a = State $ \s -> (a,s) Prelude| State x >>= f = State $ \s -> let (v,s') = x s in runState (f v) s' Prelude| Prelude> runState (return 2) 3 (2,3) return と >>= の定義が複雑に見えるが、本質は return a は引数 a をデータコンストラクタ m に包んで m a として返すだけだし、m a >>= f の場合は右項の m a からコンテナの中の a を取り出して、これに f を関数適用させた値 f a を返すが、これはデータコンストラクタ m に結果がくるまれた m b 型になる。単純モナドと State モナドの定義の違いはそのための調整を行っているだけだ。 また、>>= の右項の f はモナドの Kleisli 射であって、型は f :: a -> m b のように、引数一つをとり、モナド値 m b を返す関数だ。 これだけのポイントを押さえておけば、単純モナドも State モナドも同じ事をやっている事がわかる。 それはモナド変換子である StateT の場合もあまり事情はかわらない。StateT モナドの代数的データ型の定義と、Monad タイプクラスの多相関数である return と >>= の実装は次のようになる。 Prelude> newtype StateT s m a = StateT { runStateT :: (s -> m (a,s)) } Prelude> instance (Monad m) => Monad (StateT s m) where Prelude| return a = StateT $ \s -> return (a,s) Prelude| (StateT x) >>= f = StateT $ \s -> do Prelude| (v,s') <- x s Prelude| runStateT (f v) s' Prelude| Prelude> runStateT (return 2) 3 (2,3) StateT 型のデータがモナドになるには上の定義で十分だ。しかし、StateT モナドは MonadState クラスの get、put や MonadTrans クラスの lift などの多相関数がなければ真価が発揮できない。これらを多相関数として使うためには、StateT モナドを MonadState や MonadTrans などのタイプクラスのインスタンスにする必要があるが、動作確認のためとしては煩雑になるので、ここでは、普通の関数として定義する。 Prelude> let Prelude| get :: StateT s IO s Prelude| get = StateT $ \s -> return (s,s) Prelude| Prelude> let Prelude| put :: s -> StateT s IO () Prelude| put s = StateT $ \_ -> return ((),s) Prelude| Prelude> let Prelude| lift :: IO a -> StateT s IO a Prelude| lift c = StateT $ \s -> c >>= (\x -> return (x,s)) Prelude| get、put は基本的に State のものと同じだが、戻値が (s,s) ではなくて return (s,s) のように (s,s) をモナドでくるんで m (s,s) の形で返すようにしている。 lift c の場合はアクション c が実行されて、その結果の IO a のコンテナの値 a と状態 s の値のペア (a,s) を作り、return でモナドにくるんで m (a,s) として返している。 get、put、lift を使って StateT モナドの動作を確認してみた。 Prelude> runStateT (do x <- get; put (x*2); lift (print x); return x) 3 3 (3,6) StateT モナドの動作検証がちょっと面倒だったが、このエントリーで言いたかったのは、ユーザ定義のデータ型をモナドにするのは、それらのデータ型を Monad クラスのインスタンスにして return と >>= の実装をするだけだということだ。 モナドの機能を拡張するのためには、他のタイプクラスをつくり多相関数を実装する。ただ、その際に関数の型指定に特殊な方法を使うらしくてそこのところがまだ理解できていない。自前のモナドをきちんと作れるようになるのはまだ先のようだ。 モナドで多相関数が多用されるのは、それが、統一されたインターフェースを提供するのに便利だからだ。return 関数にしても、>>= 演算子にしても多相関数にしなくても良いが、タイプクラスを活用して多相関数を記述することによって、様々なモナドに対して統一された操作性を提供することができる。 タイプクラスを単に多相関数を定義するための仕組みだと捉えることが出来れば、newtype や class、instance などのキーワードを使った複雑な定義についても理解しやくすなる。 また、Haskell をいじってわかったのが、かなりコンパイラよりの事まで調べることができるということだ。ブラックボックスが少ないというのはユーザとしてはありがたい。
by tnomura9
| 2013-05-04 01:29
| Haskell
|
Comments(0)
|
カテゴリ
新型コロナウイルス 主インデックス Haskell 記事リスト 圏論記事リスト 考えるということのリスト 考えるということ ラッセルのパラドックス Haskell Prelude Ocaml ボーカロイド 圏論 jQuery デモ HTML Python ツールボックス XAMPP Ruby ubuntu WordPress 脳の話 話のネタ リンク 幸福論 キリスト教 心の話 メモ 電子カルテ Dojo JavaScript C# NetWalker ed と sed HTML Raspberry Pi C 言語 命題論理 以前の記事
最新のトラックバック
最新のコメント
ファン
記事ランキング
ブログジャンル
画像一覧
|
ファン申請 |
||