{-# LANGUAGE UndecidableInstances #-} -- | Basic optimization of expressions module Language.Syntactic.Constructs.Binding.Optimize where import Control.Monad.Writer import Data.Set as Set import Data.Proxy import Language.Syntactic import Language.Syntactic.Constructs.Binding import Language.Syntactic.Constructs.Condition import Language.Syntactic.Constructs.Construct import Language.Syntactic.Constructs.Identity import Language.Syntactic.Constructs.Literal import Language.Syntactic.Constructs.Tuple -- | Constant folder -- -- Given an expression and the statically known value of that expression, -- returns a (possibly) new expression with the same meaning as the original. -- Typically, the result will be a 'Literal', if the relevant type constraints -- are satisfied. type ConstFolder dom = forall a . ASTF dom a -> a -> ASTF dom a -- | Basic optimization of a sub-domain class EvalBind dom => Optimize sub ctx dom where -- | Bottom-up optimization of a sub-domain. The optimization performed is -- up to each instance, but the intention is to provide a sensible set of -- \"always-appropriate\" optimizations. The default implementation -- 'optimizeSymDefault' does only constant folding. This constant folding -- uses the set of free variables to know when it's static evaluation is -- possible. Thus it is possible to help constant folding of other -- constructs by pruning away parts of the syntax tree that are known not to -- be needed. For example, by replacing (using ordinary Haskell as an -- example) -- -- > if True then a else b -- -- with @a@, we don't need to report the free variables in @b@. This, in -- turn, can lead to more constant folding higher up in the syntax tree. optimizeSym :: Proxy ctx -> ConstFolder dom -> sub a -> Args (AST dom) a -> Writer (Set VarId) (ASTF dom (DenResult a)) -- The reason for having @dom@ as a class parameter is that many instances -- require the constraint @(sub :<: dom)@. If @dom@ was forall-quantified in -- 'optimizeSym', this constraint would not be allowed. On the other hand, it -- is not possible to add the constraint @(sub :<: dom)@ to 'optimizeSym', -- because the instance for '(:+:)' doesn't satisfy it. instance (Optimize sub1 ctx dom, Optimize sub2 ctx dom) => Optimize (sub1 :+: sub2) ctx dom where optimizeSym ctx constFold (InjL a) = optimizeSym ctx constFold a optimizeSym ctx constFold (InjR a) = optimizeSym ctx constFold a optimizeM :: Optimize dom ctx dom => Proxy ctx -> ConstFolder dom -> ASTF dom a -> Writer (Set VarId) (ASTF dom a) optimizeM ctx constFold = transformNode (optimizeSym ctx constFold) -- | Optimize an expression optimize :: Optimize dom ctx dom => Proxy ctx -> ConstFolder dom -> ASTF dom a -> ASTF dom a optimize ctx constFold = fst . runWriter . optimizeM ctx constFold -- | Convenient default implementation of 'optimizeSym' (uses 'evalBind' to -- partially evaluate) optimizeSymDefault :: ( sub :<: dom , WitnessCons sub , Optimize dom ctx dom ) => Proxy ctx -> ConstFolder dom -> sub a -> Args (AST dom) a -> Writer (Set VarId) (ASTF dom (DenResult a)) optimizeSymDefault ctx constFold sym@(witnessCons -> ConsWit) args = do (args',vars) <- listen $ mapArgsM (optimizeM ctx constFold) args let result = appArgs (Sym $ inj sym) args' value = evalBind result if Set.null vars then return $ constFold result value else return result instance (Identity ctx' :<: dom, Optimize dom ctx dom) => Optimize (Identity ctx') ctx dom where optimizeSym = optimizeSymDefault instance (Construct ctx' :<: dom, Optimize dom ctx dom) => Optimize (Construct ctx') ctx dom where optimizeSym = optimizeSymDefault instance (Literal ctx' :<: dom, Optimize dom ctx dom) => Optimize (Literal ctx') ctx dom where optimizeSym = optimizeSymDefault instance (Tuple ctx' :<: dom, Optimize dom ctx dom) => Optimize (Tuple ctx') ctx dom where optimizeSym = optimizeSymDefault instance (Select ctx' :<: dom, Optimize dom ctx dom) => Optimize (Select ctx') ctx dom where optimizeSym = optimizeSymDefault instance (Let ctxa ctxb :<: dom, Optimize dom ctx dom) => Optimize (Let ctxa ctxb) ctx dom where optimizeSym = optimizeSymDefault instance ( Condition ctx' :<: dom , Lambda ctx :<: dom , Variable ctx :<: dom , AlphaEq dom dom dom [(VarId,VarId)] , Optimize dom ctx dom ) => Optimize (Condition ctx') ctx dom where optimizeSym ctx constFold cond@Condition args@(c :* t :* e :* Nil) | Set.null cVars = optimizeM ctx constFold t_or_e | alphaEq t e = optimizeM ctx constFold t | otherwise = optimizeSymDefault ctx constFold cond args where (c',cVars) = runWriter $ optimizeM ctx constFold c t_or_e = if evalBind c' then t else e instance (Variable ctx :<: dom, Optimize dom ctx dom) => Optimize (Variable ctx) ctx dom where optimizeSym _ _ var@(Variable v) Nil = do tell (singleton v) return (inj var) instance (Lambda ctx :<: dom, Optimize dom ctx dom) => Optimize (Lambda ctx) ctx dom where optimizeSym ctx constFold lam@(Lambda v) (body :* Nil) = do body' <- censor (delete v) $ optimizeM ctx constFold body return $ inj lam :$ body'