Strictness Analysis featuring Template Haskell
Abstract interpretationベースの正格性解析(とlet-to-case transformation)をコンパイラに直接手を入れず、template haskell使って実装したぜheheという論文。
Strictness Analysis and let-to-case Transformation using Template Haskell
http://www.cs.ioc.ee/tfp-icfp-gpce05/tfp-proc/30num.pdf
割とwktkしながら読んだけど、実用に使うというよりは、ある機能を実装して実験したいけど、GHC自体に手を入れるのはめんどくさいという時に使えるかもしれないという程度っぽい
論文のコードを適当に補ったり改変したもの。とりあえず、absEval関数(論文のstrict関数)がメインで、論文のFigure3を忠実にHaskellで書いただけ
{-# OPTIONS -fth #-} module AbsInt where import Language.Haskell.TH import Data.List(lookup) import Data.Maybe(fromJust) -- basic data types data StrictAnnot = Bot | Top deriving (Eq,Show) data AbsVal = B StrictAnnot | F [StrictAnnot] | FB Int deriving (Eq,Show) instance Ord StrictAnnot where Bot <= Top = True Top <= Bot = False _<=_ = True lub, glb :: AbsVal -> AbsVal -> AbsVal lub (B Bot) val2 = val2 lub val1 (B Bot) = val1 lub (F xs) (F ys) = F (zipWith max xs ys) lub _ _ = B Top glb (F xs) (F ys) = F (zipWith min xs ys) glb (B Top) val2 = val2 glb val1 (B Top)= val1 glb _ _ = (B Bot) -- Environment type AbsEnv = [(Name , (Maybe AbsVal))] emptyEnv::AbsEnv emptyEnv = [] getEnv :: Name -> AbsEnv -> AbsVal getEnv name env = case lookup name env of Just ret -> (fromJust ret) addEnv :: (Name,AbsVal) -> AbsEnv -> AbsEnv addEnv (name,val) env = (name,Just val):env extendEnv :: AbsEnv -> [Name] -> AbsEnv extendEnv env names = (map (\n->(n,Nothing)) names)++env combines :: AbsEnv -> AbsEnv -> (AbsEnv,Bool) combines env1 env2 = case env1 of [] -> (env2,False) (e:es) -> if (lookup (fst e) env2) == Nothing then aux False $ combines es (e:env2) else aux True $ combines es env2 where aux b1 (env,b2) = (env , b1 || b2) -- sub routines isRecursiveFun ::[Dec] -> Bool isRecursiveFun decs = False --嘘! splitDecs :: [Dec] -> ([Name] , [Exp]) splitDecs = unzip.(map splitDec) splitDec :: Dec -> (Name,Exp) --適当 splitDec (ValD (VarP name) (NormalB exp) decs) = (name,LetE decs exp) splitDec (FunD name [Clause pats (NormalB body) decs]) = (name , LetE decs (LamE pats body)) addEnvPat :: AbsVal -> [Pat] -> AbsEnv -> AbsEnv addEnvPat v pats env = (map aux pats)++env where aux p = case p of (VarP n) -> (n,Just v) -- main routines absEval :: Exp -> AbsEnv -> AbsVal absEval e env = case e of (LitE lit) -> B Top (InfixE (Just e1) e (Just e2)) -> glb (absEval e1 env) (absEval e2 env) (VarE s) -> getEnv s env (CondE e1 e2 e3) -> glb (absEval e1 env) (lub (absEval e2 env) (absEval e3 env)) (LamE [VarP s] e) -> let B b = aux e (addEnv (s,B Bot) env) in case (absEval e (addEnv (s,B Top) env)) of B b1 -> F (b:[]) F bs -> F (b:bs) (ConE c) -> B Top (AppE (ConE c) e) -> absEval e env (AppE e1 e2) -> if (isCon e1) then lub (absEval e1 env) (absEval e2 env) else absApply (absEval e1 env) (absEval e2 env) (LetE ds e) -> absEval e (absFix ds env) (CaseE e ms) -> let se = absEval e env l = caseAux ms se env in (glb se (foldr lub (B Bot) l)) where aux::Exp -> AbsEnv -> AbsVal aux (LamE ((VarP s):[]) e) env = aux e (addEnv (s,B Top) env) aux e env = absEval e env isCon e = case e of {(ConE s)->True;otherwise->False}--必要性がわからん caseAux ms se env = map (caseAlt se env) ms caseAlt abs env m = case m of Match (ConP con ps) (NormalB e) [] -> absEval e (addEnvPat abs ps env) Match (VarP x) (NormalB e) [] -> absEval e (addEnvPat abs [VarP x] env) absApply::AbsVal -> AbsVal -> AbsVal absApply (FB n) a | n==1 = B Bot | n > 1 = FB (n-1) absApply (F xs) (B b) = let h = head xs tl = tail xs in if (h == Top || b == Top) then if (tl==[]) then (B Top) else F tl else if (tl==[]) then (B Bot) else (FB (length tl)) absFix:: [Dec] -> AbsEnv -> AbsEnv absFix [] env = env absFix ds env = let (varns,es) = splitDecs ds init = extendEnv env varns f (env', cont) = let aes = map (flip absEval env') es pairs = zipWith (\l r -> (l,Just r)) varns aes in (combines env' pairs) fixPoint g (env,b) = if b then (let (env',b') = g (env,b) in (fixPoint g (env',b'))) else env in fixPoint f (init ,True) strict :: ExpQ -> AbsEnv -> ExpQ strict eq env = do {e <- eq ; return (toExp(absEval e env))} where toExp v = case v of (B Top) -> (ConE (mkName "Top")) --暫定 (B Bot) -> (ConE (mkName "Bot")) --同上 (F ls) -> (AppE (ConE (mkName "F")) (ListE (map (toExp.B) ls))) (FB n) -> (AppE (ConE (mkName "FB")) (LitE (IntPrimL (toInteger n)))) -- let-to-case transformation letToCase :: Exp -> AbsEnv -> Exp letToCase (LetE ds e) env = if (isRecursiveFun ds) then let (vs,es) = splitDecs ds env' = foldr (\n e -> addEnv (n,B Top) e) env vs te' = letToCase e env' in (LetE ds te') else case (head ds) of ValD (VarP x) (NormalB e') [] -> let te' = letToCase e' env te = letToCase e (addEnv (x, B Top) env) ds' = ValD (VarP x) (NormalB te') [] F bs = absEval (LamE [VarP x] te) env in if (head bs) == Bot then (CaseE te' [Match (VarP x) (NormalB te) []]) else (LetE [ds'] te) letToCase otherexp env = otherexp transf :: ExpQ -> AbsEnv -> IO Exp transf e env = runQ $ e>>=(\x -> return $ letToCase x env) q1 = [| \x-> \y -> 3*x |] q2 = [| let a=1 in let b=2 in a+b |]
で、本体。q1,q2はMainの方で定義したいけど、それやると怒られるんだよなー。理不尽
{-# OPTIONS -fth #-} --to compile,ghc --make -O main.hs module Main where import AbsInt main = do{ putStrLn (show $(strict q1 emptyEnv)) ; e <- (transf q2 emptyEnv); putStrLn (show e) }
適当な箇所
・isRecursiveFunはlet式が再帰的定義になってるかどうか判定することになってるけど、書くのがめんどくさかったので常にFalseを返すという、やる気のなさ。
・splitDecは多少ましだけど、網羅的でない
・生のHaskell式をExp型の値に変換する方法がわからん([|*|]だと、ExpQになる)
・あと、これは論文がそうなってるんだけど、ラムダ式は一引数しか扱えない(\x->\y->3*xはOKだけど、\x y -> 3*xはOUT)。ここらへんは、まあどうとでもなる話だけど、他にも抜けがあるかもしれない
やる気がないというのもさりながら、template Haskellは、deSugarされる前のhaskell構文をできるだけ忠実に扱おうとしてるので、不必要に複雑。基本になる型はExpだけど、PatとかMatchとかDecとか把握しないといけない。そんなに覚えられないよ(覚える必要はないが)!そこらへんいくと、GHC Coreの方は大分シンプル。けど、GHC Coreはユーザが手軽に触れるようにはなってない。HyloFusionかFixed Point PromotionでもTemplate haskellで書いてみるかなぁ
あとTemplate Haskellって、templateの片鱗も見えない