Hylomorphism(その2)

よく考えると(よく考えなくても)、Hylo-fusionをRULESプラグマのみで扱うのは無理がある。普通のプログラムをHylo3つ組形式に変換→融合変換→Hylo3つ組形式を展開、というプロセスで、2つ目と3つ目をRULESで処理しようとしても、本来融合変換できるはずのものが、展開されちゃうとか、そういうことが起きそうな気がする・・・。いや、phase-controlとか利用すればできるのかも

ま、とりあえず、そのへんは無視して、lengthNTやmapNTが泥臭いので、"basic functors and related combinators"で書かないといけないとか、そういう話だった。以下の話は基本的にここに書いてある話。


まず、NatFやListFを、Eitherとtupleの組み合わせで与える必要がある。んだけど、

type NatF t = Either () t
instance Functor NatF where
 ...

とかやっても、エラーになる・・・。Haskellつかえねー。type宣言って、本当にただのaliasとしてしか働かないんだなー。


仕方ないので、IsoTypeクラスを導入して、NatF tとEither () t 間の「同形写像」とかを自分で定義することにした。が、Haskell型推論がアレなので、型を書かないと大抵推論に失敗するとか。Haskellつかえねー(2回目)。まあ、こんな感じで

{-# OPTIONS -fglasgow-exts #-}
newtype Fix f = In (f (Fix f))
out (In x) = x

--fold g = g . fmap (fold g) . out
--unfold g = In . fmap (unfold g) . g

--大人の事情で、GHCに勝手に型推論してもらうことに
{-
hylo::(Functor f , Functor g) 
       => (g b -> b)  -> (forall x.(f x)->(g x)) -> (a -> f a) -> a -> b
-}
hylo phi eta psi = phi .  eta . (fmap $ hylo phi eta psi) . psi

--簡易版
{-# RULES "hylo-shift-comp" forall p q. (hylo In p out) . (hylo In q out) = hylo In (p.q) out #-}
{-# RULES "hylo-shift"  forall p q x. hylo In p out (hylo In q out x) = hylo In (p.q) out x #-}


--型の同形
class IsoType t1 t2 where
  strip :: t1 -> t2
  equip :: t2 -> t1

{-# RULES "strip/equip" strip . equip = id #-}
{-# RULES "equip/strip" equip . strip = id #-}


--basic functors and related combinators
--cop:separated sum , cup:product
--split f g = <f,g> , junc f g = [f,g]
exl = fst 
exr = snd 
inl = Left 
inr = Right

cop :: (a -> c) -> (b -> d) -> (Either a b) -> (Either c d)
cop f g (Left x) = Left (f x)
cop f g (Right x) = Right (g x)

cup :: (a -> c) -> (b -> d) -> (a,b) -> (c,d)
cup f g (r,l) = (f r , g l)

junc :: (a -> c) -> (b -> c) -> Either a b -> c
junc f g (Left x) = (f x)
junc f g (Right x) = (g x)

split :: (a -> b) -> (a -> c) -> a -> (b,c)
split f g x = (f x , g x)

{-# RULES "hylo-redex1"  forall f g. exl . (cup f g) = f . exl #-}
{-# RULES "hylo-redex2"  forall f g. exr . (cup f g) = g . exr #-}
{-# RULES "hylo-redex3"  forall f g. exl . (split f g) = f #-}
{-# RULES "hylo-redex4"  forall f g. exr . (split f g) = g #-}
{-# RULES "hylo-redex5"  split exl exr = id #-}
{-# RULES "hylo-redex6"  forall f g h j. (cup f g) . (split h j) = (split (f . h) (g . j)) #-}
{-# RULES "hylo-redex7"  forall f g h j. (cup f g) . (cup h j) = (cup (f . h) (g . j)) #-}
{-# RULES "hylo-redex8"  forall f g h. (split f g) . h = (split (f . h) (g . h)) #-}
{-# RULES "hylo-redex9"  forall f g. (cop f g) . inl = inl . f #-}
{-# RULES "hylo-redex10" forall f g. (cop f g) . inr = inr . g #-}
{-# RULES "hylo-redex11" forall f g. (junc f g) . inl = f #-}
{-# RULES "hylo-redex12" forall  f g. (junc f g) . inr = g #-}
{-# RULES "hylo-redex13" junc inl inr = id #-}
{-# RULES "hylo-redex14" forall f g h j. (junc f g) . (cop h j) = (junc (f . h) (g . j)) #-}
{-# RULES "hylo-redex15" forall f g h j. (cop f g) . (cop h j) = cop (f . h) (g . j) #-}
{-# RULES "hylo-redex16" forall f g h. f . (junc g h) = (junc (f . h) (g . h)) #-}


--Nat型定義
data NatF t = ZeroF | SucF t

instance Functor NatF where
 fmap f ZeroF = ZeroF
 fmap f (SucF n) = SucF(f(n))

type Nat = Fix NatF
zero = In ZeroF
suc = In . SucF

instance IsoType (NatF t) (Either () t) where
 strip ZeroF = Left ()
 strip (SucF p) = Right p
 equip (Left ()) = ZeroF
 equip (Right p) = (SucF p)

instance Show Nat where
 show n = show $ nat_to_int n

nat_to_int::Nat->Integer
nat_to_int (In ZeroF) = 0
nat_to_int (In (SucF p)) = 1 + nat_to_int(p)


--List型定義
data ListF a t = NilF | ConsF a t
instance Functor (ListF a) where
 fmap f NilF = NilF
 fmap f (ConsF x y) = ConsF x (f y)

instance IsoType (ListF a t) (Either () (a,t)) where
 strip NilF = Left ()
 strip (ConsF a x) = Right (a,x)
 equip (Left ()) = NilF
 equip (Right (a,x)) = (ConsF a x)

type List a = Fix (ListF a)
nil = In NilF
cons x xs = In (ConsF x xs)

ltol :: List a -> [a]
ltol (In NilF) = []
ltol (In (ConsF x xs)) = x:(ltol xs)

_map f = hylo In (mapNT f) out
mapNT :: (a -> b) -> (ListF a t) -> (ListF b t)
mapNT f = (equip::(Either () (a,t))->(ListF a t)) 
            . (cop id (cup f id)) 
            . (strip::(ListF b t) -> (Either () (b,t)))

_length = hylo In lengthNT out
lengthNT :: (ListF a t) -> (NatF t)
lengthNT = 
   equip . (cop id exr ) . (strip::(ListF a t) -> (Either () (a,t)))

ls = cons (suc zero) $ cons zero nil
main = print $ _length $ _map suc ls

で、こうしておけば

_length . (_map f) 
= {since dinition}
(hylo In (equip.(cop id exr).strip) out).(hylo In (equip.(cop id (cup f id)).strip) out)
= {since hylo shift law}
hylo In (equip.(cop id exr).(cop id (cup f id)).strip) out
= {since "hylo-redex15"}
hylo In (equip.( cop (id.id) (exr.(cup f id)) ).strip) out
= {since "hylo-redex2"}
hylo In (equip.(cop (id.id) (id.exr)).strip) out
= {since basic property of id}
hylo In (equip.(cop id exr).strip) out
= {By definition}
_length

とかなって、_length . (_map f) = _lengthくらいは、簡単に証明できる。。


というようなことを、わざわざ手で書いたのは、つまりGHCのRULESプラグマではうまくいかなかったからなんだけど

Main.lvl8 = Main.poly_hylo
              @ (Main.Fix (Main.ListF (Main.Fix Main.NatF)))
              @ (Main.ListF (Main.Fix Main.NatF) (Main.Fix (Main.ListF (Main.Fix Main.NatF))))
              @ (Main.Fix (Main.ListF (Main.Fix Main.NatF)))
              (Main.$WIn @ (Main.ListF (Main.Fix Main.NatF)))
              Main.lvl4
              (Main.out @ (Main.ListF (Main.Fix Main.NatF)))
              Main.lvl7

Main.lvl9 :: Main.Fix Main.NatF
[GlobalId]
[]
Main.lvl9 = Main.poly_hylo
              @ (Main.Fix Main.NatF)
              @ (Main.NatF (Main.Fix Main.NatF))
              @ (Main.Fix (Main.ListF (Main.Fix Main.NatF)))
              (Main.$WIn @ Main.NatF)
              Main.lvl3
              (Main.out @ (Main.ListF (Main.Fix Main.NatF)))
              Main.lvl8

ここまできておきながら〜!!Haskellつかえねー(3回目)。Conal Elliottが、どっかでGHCのsimplifierは複雑すぎて、展開されてほしいものがされなかったりして、わけわからんので、ダメぽとか書いてたけど、う〜ん(まあ、私がダメだとかいう可能性も否定できないけど)。hylo-redexはいけるようにみせかけて、実は、上で定義したRULESは関係ないとか。GHC本体の機能のみで勝手に最適化される(まあ、基本的にtrivialなルールなので、それはそれでいいんだと思う)

hylo-fusionは、fold/buildルールによるshortcut-fusionよりは、大分見通しがいいと思うけどな〜。でも、別に人間が手でやるのではなく、自動化したいので、見通しのよさは関係ないんだけど。しかし、RULESプラグマが使えないとなると、やっぱGHCに直接手を入れるしかないのか?それはしたくないよなー。どう考えても大変そうだし。あと、別の手として、-fext-coreで吐いたCoreに処理を施すという道がある。ExtCoreをRead/Writeする関数は、GHCに既にあるのでこれが一番楽かもしれない