🔙 Back to Top

Sat Mar 23 18:32:54 JST 2013

State モナド

Stateモナドって結局、値と状態のタプルを持って回ってるだけで、 大げさなことやるわりに大したことない

Control.Arrowを昨日読解してた 実際、これもやることは大したことない でも大げさに行わない分、分かりやすい

Arrowを使って、スタックを実装する (値、スタック) というタプルを >>> で流すだけだけど

-- import Control.Monad.State
import Control.Arrow

(|>) x f = f x
infixl 0 |>

start :: (Int, [Int])
start = (0, [])

push :: Int -> (a, [Int]) -> (Int, [Int])
push n = const n *** (n:)

pop  :: (a, [Int]) -> (Int, [Int])
pop (_, (x:xs)) = (x, xs)
*Main> start |> push 1
(1,[1])
*Main> start |> push 1 >>> push 2
(2,[2,1])
*Main> start |> push 1 >>> push 2 >>> pop
(2,[1])

問題ない

いくつかのArrowを使ったコードではたいてい

let f = hoge >> hage >> moge
f 3

みたいに使ってるからいいけど、

(hoge >>> hage >>> moge) 3

とかかっこ悪くて、上の |> を使えば

3 |> hoge >>> hage >>> moge

とかけてすごいパイプラインぽい で、|>を左結合にしたので

3 |> hoge |> hage |> moge

としても同じ意味になる.個人的にはこちらのほうが好き

ただスタックを作っただけじゃ勿体ないからスタックの上での計算機を 作ってみる

足し算なら、二度ポップして出てきたのを足してプッシュする 一度ポップしたものは (a, state) のaとして保持されるけど、そのまま二度目のポップをしたらaは破棄されるので なんとかして別のものとして持っとく必要があるけれど、これはまさしくArrowで 言う、パイプラインの分岐だ

*Main> start |> push 1 |> push 2 |> pop
(2,[1])
*Main> start |> push 1 |> push 2 |> pop |> fst &&& pop
(2,(1,[]))
*Main> start |> push 1 |> push 2 |> pop |> fst &&& pop |> (\(x,ys@(y,s)) -> push (x+y) ys)
(3,[3])

appの存在はしってるけど、直接タプルを受け取るような関数を書いた方が早いしコード短いよね

従って、次のような汎用のスタックの上で動く二項演算子が作れる

op f =
    pop >>> fst &&& id >>> second pop
    >>> (\(x, ys@(y,s)) -> push (f x y) ys)

add = op (+)
sub = op (-)
mul = op (*)

*Main> start |> push 2 |> push 3 |> add
(5,[5])
*Main> start |> push 2 |> push 3 |> push 7 |> add |> mul
(20,[20])

頭に runState とか書くの煩わしいし、コレでいいよね、普通に