セカイノカタチ

世界のカタチを探求するブログ。関数型言語に興味があり、HaskellやScalaを勉強中。最近はカメラの話題も多め

モナドの分解ふたたび

懲りずにモナドを分解していきます。

以前にモナドを図解してみたんだけど、同じノリで今度は普通にソースコードベースで分解していきたい。

前回の教本は「プログラミングHaskell」だったけど、今回は「すごいHaskell楽しく学ぼう!」から。

早速、P335を見てみよう。

newtype State s a = State { runState :: s -> (a, s) }
instance Monad (State s) where
    return x = State $ \s -> (x, s)
    (State h) >>= f = State $ \s -> let (a, newState) = h s
                                        (State g) = f a
                                    in g newState

さて、そろそろモナドの定義もなれてきたぞ。
返り値であるところの、「s -> (x, s)」が関数になっていて、引数を受け付けるあたりは、IOモナドと一緒だね。
このモナドを連結してできるでっかいモナドは、最終的に関数を返して、その関数に渡された引数が、モナドの連結を逆にたどり、ステータスを更新しながら、最終的な戻り値になるはず。

P336では、実装例としてスタックが挙げられている。定番?

type Stack = [Int]
pop :: State Stack Int
pop = state $ \(x:xs) -> (x, xs)
push = Int -> State Stack ()
push a = state $ \xs -> ((), a:xs)

むむむ。

利用方法は、こんな感じ。

stackMainp :: State Stack Int
stackMainp = do
    push 3
    a <- pop
    pop

出ましたdo記法。書く分には優雅でエレガント(?)なのだが、どうも分かりにくいので、バインド(>>=)で書き直してみる。

stackMainp' = push 3 >>= \_ -> pop >>= \a -> pop

ポイントは、do記法では、2行目の「a <- pop」がセットのように見えていたけど、実際は、3行目のpopと同じラムダに含まれる。
letを使って、各処理の継目をはっきりさせよう。(´・ω・`)

stackMainp'' = let x = push 3
                   y = (\_ -> pop)
                   z = (\a -> pop)
               in x >>= y >>= z

do記法と似たような形になったが、do記法に比べて各行の処理の分かれ目が、わかりやすいのではないかと思う。
さらに各行の型を明示するとこうなる。

stackMainp'' = let x = push 3 :: State Stack ()
                   y = (\_ -> pop) :: () -> State Stack Int
                   z = (\a -> pop) :: Int -> State Stack Int
               in x >>= y >>= z

2行目の型が、Int -> State..ではなく、()が引数になっている。これは、push 3の戻りの型に合わせるべく、Haskell型推論した結果だ。do記法では隠されてしまっているところで、高度な言語の能力が発揮されているのだ。

引数無いじゃん?

皆さん、不思議に思いませんか?僕は思いました。
この関数って引数無いじゃん。(`・ω・´)

戻り値のState Stack Intは、(Stack -> (Int, Stack))という関数を包んだ型になっている。

newtype State s a = State { runState :: s -> (a, s) }

これは、厳密にはモナド関係無いっぽいけど、モナドとの組み合わせでよく出てくるパターンで、IOモナドもこのパターンになっている。
そして、このパターンのモナドの中には、同じくState Stack aを戻り値とする関数群がバインド(>>=)によって連結されて格納されているというパターンになり、フラクタルのような自己相似な構造になっている。
IOモナドであれば、mainの中にIOを戻り値とする関数を書いていくが、どれだけ複雑で巨大なシステムでも、バインド(>>=)の連結とIOを戻す関数の集合になっている。

Stateであれば、Stateモナドで包まれた範囲でそれが起こる。IOとの違いは、中身の関数を取り出せることだ。(IOは、mainの外にいる大いなる存在だけが、IOの中身にアクセスできる)
それが、runStateというアクセス関数になる。
runStateをStateモナドに適用すると関数が取り出せる。やってみよう。

ghci> :t runState stackMainp
runState stackMainp :: Stack -> (Int, Stack)

この取り出した無名関数にStack(という名の[Int])を渡して評価すると、モナド演算が走る。

ghci> runState stackMainp [1,2,3]
(1,[2,3])

どや。(  ゜Д ゜)

今回の結論

今回はモナドっぽい話が出来た気がする。本当によくできた仕組みだ。
Haskell上級者は、どんな頭になっているのだろうか。

モナドの仕組みを調べていくと、レゴの部品のような図形のイメージが頭にチラチラ浮かんでくる。統一された連結部分と個別の機能を持ったブロックを組み上げていく様は、うまくモナドをメタファ出きるんじゃないかと直感するんだけど、今のところ図に描き表すことが出来ていない。><

うまく描くことが出来たなら、またブログに描きたいと思う。(///)