MonadFixの理解のために(2)---リストモナドでmfix考える4
ここまではpairwise swapという具体的なもので考えてきたので,こんどは抽象的に攻めてみます.MonaFixのインスタンスのmfixには満たすべき性質があるのでした.
-- purity mfix (return . h) = return (fix h) -- left shrinking (or tightening) mfix (\x -> a >>= \y -> f x y) = a >>= \y -> mfix (\x -> f x y) -- sliding mfix (liftM h . f) = liftM h (mfix (f . h)), for strict h. -- nesting mfix (\x -> mfix (\y -> f x y)) = mfix (\x -> f x x)
これらの性質をリストモナドのmfix
mfix f = case fix(f.head) of [] -> [] x:_ -> x:mfix (tail.f)
が満たすことを(かなりアバウトに)検証してみます.
purity
-- h:: a->a -- mfix::(a->[a])->[a] mfix (return . h) = return (fix h)
を考えます.
(return.h) x = [h x] (head.return.h) x = h x fix (head.return.h) = fix h
だから,case fix (head.return.h)は
fix h : mfix (tail.return.h)
となって,tail.return.h = []なので,
mfix (return . h) = [fix h] = return (fix h)
が成り立ちます.
これはmfixがモナドが出てこない「普通の関数」に対してはmfixはfixと同じであることを示しています.
left shrinking(またはtightening)
-- f:: a -> b -> [c] -- mfix::(d->[d])->[d] mfix (\x -> [b1,b2,...] >>= \y -> f x y) = [b1,b2,...] >>= \y -> mfix (\x -> f x y)
ちょっと表記を変えました.xの型はa,yの型はbとしています.
\x-> [b1,b2,...] >>= \y -> f x yを名前をつけてg x = [f x b1,f x b2,...]と表現し,mfix gを考えます.
(g.head) [x,...] = [f x b1,f x b2,...] fix (g.head) = x where f x b1 = x
なので,
mfix g = x1 : mfix(tail.g) where f x1 b1 = x1
となります.これを繰り返していくことで
mfix g = [x1, x2, x3, ...] where xi = f xi bi (i=1,2,3,...) mfix g = [x1, x2, x3, ...] where xi = fix (\x -> f x bi) (i=1,2,3,...)
ここで,\y -> fix (\x->f x y) を考えれば,リストモナドの>>=がconcatMapであることから
mfix g = [x1, x2, ...] = [b1,b2,...] >>= \y -> fix (\x->f x y)
つまり,
-- left shrinking (or tightening) mfix (\x -> a >>= \y -> f x y) = a >>= \y -> mfix (\x -> f x y)
が成り立つことが分かります.
これは,不動点の計算の対象とはならない変数(aとy)は>>=ごとmfixの左に追い出すことができることを意味します.mfixの対象を左側に追い出して小さくする(shrink),引き締める(tighten)ということでしょうか.
以下,正直に書くと記号が大氾濫するので,かなりおおざっぱに検証します.まじめにやるとすごいことになってかけません。。。多分。
sliding
-- f:: r -> m a -- mfix :: (r-> m r)-> m r mfix (liftM h . f) = liftM h (mfix (f . h)), for strict h::a->r. liftM :: (Monad m) => (a -> r) -> m a -> m r liftM h m1 = do x1 <- m1 return (h x1)
左辺をざっくり計算すると
f r1 = [a1,a2,...] (liftM h. f) r1 = [h a1, h a2, ....] (liftM h.f) $ head [r1, r2,...] = [h a1, h a2, ....] fix ((liftM h.f). head) = [h a1, h a2, ...] = [r1, r2, ...] mfix (liftM h. f) = h a1: mfix (tail.liftM.f)
一方,右辺をざっくり計算すると
f.h.head [a1,a2,....] = f (h a1) = f r1 = [a1,a2,...] fix (f.h.head) = [a1,a2,...] = f (h a1) mfix (f.h) = a1:mfix (tail.f.h) lisftM h mfix(f.h) = h a1:liftM h mfix (tail.f.h)
ということなので,成立しているのが分かります.
liftM hが外にでてくるということでslidngということでしょうか.
nesting
-- f:: a -> a-> m a -- mfix :: (a-> m a)-> m a mfix (\x -> mfix (\y -> f x y)) = mfix (\x -> f x x)
文字がいり乱れますが・・・右辺から.
k x = f x x fix(k.head)= [c1,c2,..] = f c1 c1 mfix k = c1:mfix(tail.k)
としておくと
f c1 = g g.head [c1,c2,...] = f c1 c2 = [c1,c2,..] fix(g.head) = [c1,c2,...] h = \x -> mfix (\y -> f x y) h.head [c1,c2,...] = c1:mfix(tail.g) fix(h.head)=[c1,...] mfix h = c1:...
のような具合で,やはり成り立っていることが分かります.
・・・しっかし,自分が何をやってるのかわかんなくなってきた.そもそもコラッツの計算でHaskellでのメモ化が知りたくなって,Stateモナドを調べ初めて,そこででてきたMonadFixではまって,リストモナドでMonadFixはdo記法の再帰版だということに流れ着いてきたのだったが・・・寄り道しすぎて戻れなくなった.自爆ですね.いい加減Project Eulerに戻るかな・・・
[Haskell] 逆引き
sampou.orgに「逆引き」のコーナーができています.なんか私がごちゃごちゃいったのが始まりなのかなと思いつつ,あのままだとさすがに使いにくいというか,まだ種の段階ですよね.どうすると実用的になるのでしょうか?
- 型を明記する
- 具体例をつける
- 項目ごとに分割する
- 市販の書籍のような体裁にしてみる?(PDF)
。。。考えてみる価値はありそうです.