Hylomorphism(その3)

結局、ダメなのは私だったという話。コメントで教えていただいた通りやったらできた。

{-# OPTIONS -fglasgow-exts #-}
newtype Fix f = In (f (Fix f))
out (In x) = x
{-# NOINLINE out #-} --必要ないけど、Core出力見るときに便利とか

--確認用
{-
fold :: forall f.(Functor f) => (forall x.(f x -> x) -> (Fix f) -> x)
fold g = g . fmap (fold g) . out
unfold :: forall f.(Functor f) => (forall x.(x -> f x) -> x -> (Fix f))
unfold g = In . fmap (unfold g) . g
fold f  = hylo f id out
unfold f = hylo In id f
-}

type NT f g = forall x.f x -> g x
hylo::(Functor f , Functor g) => (g b -> b)  -> (NT f g) -> (a -> f a) -> a -> b
hylo phi eta psi = phi .  eta . (fmap $ hylo phi eta psi) . psi

--簡易版
{-# RULES "hylo-shift" 
    forall (p :: forall x. g x -> h x) (q :: forall x. f x -> g x) a. 
      hylo In p out (hylo In q out a) = hylo In (p.q) out a #-}


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

--{-# RULES "strip/equip" forall x . strip  (equip x) = x #-}
--{-# RULES "equip/strip" forall x . equip  (strip x) = x #-}


--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 x. exl (cup f g x) = f (exl x) #-}
{-# RULES "hylo-redex2"  forall f g x. exr (cup f g x) = g (exr x) #-}
{-# RULES "hylo-redex3"  forall f g x. exl (split f g x) = f x #-}
{-# RULES "hylo-redex4"  forall f g x. exr (split f g x) = g x #-}
{-# RULES "hylo-redex5"  split exl exr  = id #-}
{-# RULES "hylo-redex6"  forall f g h j x. cup f g (split h j x) = (split (f . h) (g . j) x) #-}
{-# RULES "hylo-redex7"  forall f g h j x. cup f g (cup h j x) = (cup (f . h) (g . j) x) #-}
{-# RULES "hylo-redex8"  forall f g h x. split f g (h x) = split (f . h) (g . h) x #-}
{-# RULES "hylo-redex9"  forall f g x. cop f g (inl x) = inl (f x) #-}
{-# RULES "hylo-redex10" forall f g x. cop f g (inr x) = inr (g x) #-}
{-# RULES "hylo-redex11" forall f g x. junc f g (inl x) = f x #-}
{-# RULES "hylo-redex12" forall f g x. junc f g (inr x) = g x #-}
{-# RULES "hylo-redex13" junc inl inr = id #-}
{-# RULES "hylo-redex14" forall f g h j x. junc f g (cop h j x) = junc (f . h) (g . j) x #-}
{-# RULES "hylo-redex15" forall f g h j x. cop f g (cop h j x) = cop (f . h) (g . j) x #-}
--hylo-redex16はhがstrictでないと成立しないので、コメントアウトしておく
--{-# RULES "hylo-redex16" forall f g h. h .(junc f g) = junc (h.f) (h.g) #-}

{-# NOINLINE [1] exl #-}
{-# NOINLINE [1] exr #-}
{-# NOINLINE [1] cop #-}
{-# NOINLINE [1] cup #-}
{-# NOINLINE [1] junc #-}
{-# NOINLINE [1] split #-}
{-# NOINLINE [1] inl #-}
{-# NOINLINE [1] inr #-}

--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 = stripList
 equip = equipList

stripList NilF = Left ()
stripList (ConsF a x) = Right (a,x)
equipList (Left ()) = NilF
equipList (Right (a,x)) = ConsF a x


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

_map f = hylo In (mapNT f) out
 where
  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
 where
  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)
--main = print $ _length $ _map suc ls 
--main = print $ _length . (_map suc) $ ls


Coreが

Main.lvl3 :: forall x_a2jv.
             Main.ListF (Main.Fix Main.NatF) x_a2jv -> Main.NatF x_a2jv
[GlobalId]
[Arity 1
 NoCafRefs]
Main.lvl3 = \ (@ x_a2jv) (x_a2TY :: Main.ListF (Main.Fix Main.NatF) x_a2jv) ->
              case (Main.NatF x_a2jv) x_a2TY of wild_X1v {
                Main.NilF -> Main.ZeroF @ x_a2jv;
                Main.ConsF a_a1d9 x1_a1da -> Main.SucF @ x_a2jv x1_a1da
              }

------------------------------------

Main.lvl7 :: Main.Fix Main.NatF
[GlobalId]
[]
Main.lvl7 = Main.hylo1
              (Main.$WIn @ Main.NatF)
              Main.lvl3
              (Main.out @ (Main.ListF (Main.Fix Main.NatF)))
              Main.lvl6

こんな感じに。勝った!第三部完!


hylo shiftさえうまく発動すれば、strip/equipの消去くらいは、GHCが頑張ってやってくれるような気もする。確信はないが(それでいいのか)。問題は、"_length $ _map suc ls"や"_length . (_map suc) $ ls"だとうまくいかないっぽい。Simplifier phase 2で、($)や(.)が展開されると同時に、hyloが引数に応じてpoly_hylo_s2RWとpoly_hylo_s2s0に特殊化(?と呼ぶのかなんと言えばいいのか)されて、RULESが適用されなくなってる感じっぽい。う〜ん、RULESプラグマは面白いと思うんだけど、使いこなすの大変だなー。length/mapくらいはうまくいったけど、もっと複雑になったらどうなるやら。結局合流性が保証されない限りは、ユーザの意図と違う変換が起こるのは避けられない。まあ、この手の悩みは、OnoueのD論にもグチグチ書いてあって、ある最適化の前に、別の最適化を挿入すると、本来最適化されるはずのものが最適化されなくなったりetc...単純な項書換え系なら、合流性を保証することもできるかもしれないけど(完備化が停止するかどうかはわからんけど)、最適化には、多分項書換えで書けない類の大域的な変換(strictness analysisとか)も含まれてるだろうし。このへんは、ad hocにやってたのでは、どうにもならんかもわからんね


よいニュース。RULESプラグマのforallで束縛した変数に型が指定できるというのは、僥倖。これによって、(practical)Acid Rain Theorem(つまり、cata-hylo fusionlawとhylo-ana fusion law)が自然に書ける

{-# RULES "cata/hylo" 
  forall (tau::forall a.(f a -> a)->f' a -> a) 
         (eta1::forall a.g a->f a) (eta2::forall a.f' a -> h a) phi psi x.
          hylo phi eta1 out (hylo (tau In) eta2 psi x) = hylo (tau (phi.eta1)) eta2 psi x #-}

{-# RULES "ana/hylo"
  forall (sigma::forall a.(a -> f a)-> a ->f' a) 
         (eta1::forall a.g a->f a) (eta2::forall a.f' a -> h a) phi psi x.
          hylo phi eta1 (sigma out) (hylo In eta2 psi x) = hylo phi eta1 (sigma (eta2.psi)) x #-}

ながいよ!ちゃんと機能するのかい、これ・・・。まあ、tauとかsigmaの型が指定できてよかったという話。尤も、それできないと、fold/buildルール書けないじゃん!って話で

{-# RULES
"fold/build" 	forall k z (g::forall b. (a->b->b) -> b -> b) . 
		foldr k z (build g) = g k z

"foldr/augment" forall k z xs (g::forall b. (a->b->b) -> b -> b) . 
		foldr k z (augment g xs) = g k (foldr k z xs)

........

"augment/build" forall (g::forall b. (a->b->b) -> b -> b)
		       (h::forall b. (a->b->b) -> b -> b) .
		       augment g (build h) = build (\c n -> g c (h c n))
"augment/nil"   forall (g::forall b. (a->b->b) -> b -> b) .
			augment g [] = build g
 #-}

これ何回も見てるのに、気付いてなかったわたしがアホ


ようやく、parametricity theoremというのが理解できてきた(今まで理解せず何を書いてたんだ?)けど、Acid Rain theoremの証明には、parametricity theoremが使われてるので、seqとかが絡んでくると、Acid Rain theoremは破れることになるのか?自信ないけど、例は少し考えれば構成できるはず(参考)