stackoverflow の 「エラー処理のできる state モナドはどう記述したらいいですか。」という質問の答が面白そうだったので実験してみた。返答されたプログラムを ghci で実験しやすいように少し変更したものが次のプログラム statemonad.hs だ。
newtype StateMonad e a = StateMonad {runStateMonad :: (State -> Either e (a, State))} instance Monad (StateMonad e) where (StateMonad p) >>= k = StateMonad $ \s0 -> case p s0 of Right (val, s1) -> let (StateMonad q) = k val in q s1 Left e -> Left e return a = StateMonad $ \s -> Right (a, s) data State = State { log :: String , a :: Int } deriving Show 実行例は次のようになる。 Prelude> :l statemonad.hs [1 of 1] Compiling Main ( statemonad.hs, interpreted ) Ok, modules loaded: Main. *Main> let state = State "hello" 1 *Main> let throwError e = StateMonad $ \s -> Left e *Main> runStateMonad (return "success") state Right ("success",State {log = "hello", a = 1}) *Main> runStateMonad (throwError "error") state Left "error" コードを追いかけてみた。 まず、モナドになるデータ型を作る必要がある。これは newtype キーワードで行う。newtype は制限付きの data 宣言と考えると分かりやすい。その制限とはコンストラクタは1種類で、コンストラクタのパラメータも1つだけだ。このプログラム例の場合はタイプ名が StateMonad e a でコンストラクタは StateMonad でコンストラクタのパラメータは ((State -> Either e (a, State)) だ。パラメータが無名関数になるのはモナドではよく使われるテクニックだ。 newtype StateMonad e a = StateMonad (State -> Either e (a, State)) プログラム例とは順序が前後するが、状態の State 型は次のようになる。 data State = State { log :: String , a :: Int } deriving Show プログラムの中心は MonadState を Monad クラスのインスタンスにして >>= と return の実装を行う部分だ。型名は StateMonad e a でモナド値のコンテナの値は a 型だから、 StateMonad e 型を Monad クラスのインスタンスにする。 instance Monad (StateMonad e) where >>= の実装は次のようになる。順番に読んでいく。(StateMonad p) >>= k = が >>= 演算子の定義の部分だ。(StateMonad p) というパターンマッチで StateMonad 値のコンテナから無名関数 p を取り出す。 (StateMonad p) >>= k = StateMonad $ \s0 -> は s0 以下の無名関数を StateMonad コンストラクタでラッピングする事を示している。s0 の型は状態の State 型だ。 StateMonad $ \s0 -> case p s0 では >>= の左項の StateMonad 値から取り出した状態を引数とする無名関数 p を状態 s0 に関数適用している。その結果はエラーが発生しなければ Right (val, s1) が 、エラーが発生したときは Left e が返される。 case p s0 of Right (val, s1) が返ってきたときは、val に関数 k を関数適用した結果を (StateMonad q) のパターンマッチからコンテナの関数 q を取り出し、新しい状態 s1 に関数適用させる。 Right (val, s1) -> let (StateMonad q) = k val in q s1 Left e が返ってきたときは、単に Left e を返す。 Left e -> Left e 最後は return 関数の定義だが、 \s -> Right (a, s) を StateMonad にラッピングして返すだけだ。 return a = StateMonad $ \s -> Right (a, s) このプログラムでは Error モナドも State モナドも import されていない。Monad クラスのインスタンスを作って >>= と return を実装するだけで、State モナドと Error モナドの複合モナドの機能がプログラムできている。 この簡単なプログラム例を調べると、モナド・トランスフォーマーを使った複合モナドの作り方がブラックボックスではなくなるかもしれない。 自前のモナドを作るといっても要するにコンストラクタを定義して、そのデータ型に対して >>= と return の定義をするだけなのだ。なんか拍子抜けするが、意外に簡単なしくみだ。
by tnomura9
| 2013-08-27 21:55
| Haskell
|
Comments(0)
|
カテゴリ
新型コロナウイルス 主インデックス Haskell 記事リスト 圏論記事リスト 考えるということのリスト 考えるということ ラッセルのパラドックス Haskell Prelude Ocaml ボーカロイド 圏論 jQuery デモ HTML Python ツールボックス XAMPP Ruby ubuntu WordPress 脳の話 話のネタ リンク 幸福論 キリスト教 心の話 メモ 電子カルテ Dojo JavaScript C# NetWalker ed と sed HTML Raspberry Pi C 言語 命題論理 以前の記事
最新のトラックバック
最新のコメント
ファン
記事ランキング
ブログジャンル
画像一覧
|
ファン申請 |
||