MonadFixの理解のために(1)---fixを考える3

しつこく再帰処理とfixに粘着します.果たして,mfixにたどり着くのか(苦笑).

repmin(A recursive do for Haskell, by Levent Erk\"ok, John Launchbury, Haskell Workshop 2002, pages: 29-37. Pittsburgh, Pennsylvaniaより)

次のお題は「repmin」です.repminとは,(二分)木のリーフの値を,すべてのリーフの値の最小値(min)に置き換える(replace)問題のことです.木を

data Tree a = L a | N (Tree a) (Tree a) deriving Show

と定義すると,Lがリーフの構成子,Nがノードの構成子となり,木は,例えば

(N (L 2) (N (L 5) (N (N (L 10) (L 9) ) (L 3) ))

というように表現されます.この木のリーフの値で最小なのは「2」なので,この木から

(N (L 2) (N (L 2) (N (N (L 2) (L 2) ) (L 2) ))

を作るということになります.もちろん,すべてのリーフを辿って最小値を計算してから,もう一度置き換えていくという2パスならほとんど自明です.

-- data Tree a = L a | N (Tree a) (Tree a) deriving Show

getvals :: Tree a -> [a]
getvals (L a) = [a]
getvals (N l r) = (getvals l)++(getvals r)

rep :: Tree a -> Tree a
rep (L a) m   = L m
rep (N l r) m = N (rep l m) (rep r m)

repmin' :: (Ord a)=>Tree a -> Tree a
repmin' t = rep t (minimum $ getvals t)

これを1パスで処理するというわけです.見事で綺麗な解答があります.

-- data Tree a = L a | N (Tree a) (Tree a) deriving Show
copy :: Tree Int -> Int -> (Tree Int, Int)
copy (L a)   m = (L m, a)
copy (N l r) m = let (l', ml) = copy l m
                     (r', mr) = copy r m
                 in (N l' r', ml `min` mr)

repmin :: Tree Int -> Tree Int
repmin t = let (t', m) = copy t m in t'

これまた騙されたようなコードですが,遅延評価できっちり動きます.

repminに木tを与えると,リーフに関しては,その値aを「外」にだし,mを中にいれます.しかし,mは必要ないので評価されません.二つのリーフからなるノードにおいては,各リーフに関してその値がそれぞれ「外」にでており,中にはmが入り込んでます(が,もちろんmは評価されません).そして,外に出された値が比較され,小さい方が採用されて新しいノードが出来上がります.

これを再帰的に繰り返すことで「中身が全部mの木」と「中身だった値の最小値」のタプルが出来上がります.これを(t',m)とおくので,ここで始めてmが「中身だった値の最小値」として確定して,結果,「中身が全部mの木」t'も「中身が全部「最小値」」の木となり,このt'を返せば計算完了です.

まず秀逸なのはcopyです.copy単体だと

*Main> copy (N (L 2) (N (L 5) (N (N (L 10) (N (L 1) (L 4)) ) (L 3) ))) 100
(N (L 100) (N (L 100) (N (N (L 100) (N (L 100) (L 100))) (L 100))),1)

となります.最小値が外にでてきて,与えた引数が中に入り込みます.100は100である必要は全くありません.ですので,任意の値mを「とりあえず」与えておいて(遅延評価だからそのまま放置されます),そのmを「外にでてきた最小値」にしてしまうという細工がrepminの中のletです.こうして,期待した動作が実現できるという段取りです.

さて,関数repminは二重に再帰になってます.repminそのものが再帰,さらに使われているcopyも再帰です.あえて,fixを使ってみます.

fix f = let x =f x in x

g:: (Tree Int -> Int -> (Tree Int, Int))-> (Tree Int -> Int -> (Tree Int, Int))
g cp = \ t m  -> case t of
                  (L a)   -> (L m, a)
                  (N l r) -> let (l',ml) = cp l m
                                 (r',mr) = cp r m
                             in (N l' r', ml `min` mr)

copy:: Tree Int -> Int -> (Tree Int, Int)
copy = fix g

repmin :: Tree Int -> Tree Int
repmin t = (fst.(copy t)) (fix $ (snd.(copy t)) )

。。。copyはともかく,repmin本体はやっぱり難読です.letの偉さが分かります.タプル(alistという方がいいかな)の片方だけが再帰というか不動点なので,そこだけ分離してfixにいれたのですが,分かりにくいです.copyの方は今までの例と同じです.遅延パターンの必要はないことと型に注意です.