syntactic-0.7: Language/Syntactic/Constructs/Binding/Optimize.hs
{-# 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.Symbol
import Language.Syntactic.Constructs.Literal
import Language.Syntactic.Constructs.Condition
import Language.Syntactic.Constructs.Tuple
import Language.Syntactic.Constructs.Binding
-- | 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
-> HList (AST dom) a
-> Writer (Set VarId) (ASTF dom (EvalResult 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 (InjectL a) = optimizeSym ctx constFold a
optimizeSym ctx constFold (InjectR 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
-> HList (AST dom) a
-> Writer (Set VarId) (ASTF dom (EvalResult a))
optimizeSymDefault ctx constFold sym@(witnessCons -> ConsWit) args = do
(args',vars) <- listen $ mapHListM (optimizeM ctx constFold) args
let result = appHList (Symbol $ inject sym) args'
value = evalBind result
if Set.null vars
then return $ constFold result value
else return result
instance (Sym ctx' :<: dom, Optimize dom ctx dom) => Optimize (Sym 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
, ExprEq dom
, 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 ctx 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 (inject 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 $ inject lam :$: body'