{-# LANGUAGE PartialTypeSignatures #-}
{-@ LIQUID "--reflection" @-}
{-@ LIQUID "--ple" @-}
{-@ LIQUID "--diff" @-}
{-@ infixr ++ @-} -- TODO: Silly to have to rewrite this annotation!
module Opt where
import Prelude hiding ((++), const, max, or)
asimp :: AExp -> AExp
lemma_asimp :: AExp -> State -> _
cplus :: Int -> AExp -> AExp
lemma_cplus :: Int -> AExp -> State -> _
nplus :: AExp -> AExp -> AExp
lemma_nplus :: AExp -> AExp -> State -> _
norm :: AExp -> AExp
lemma_norm :: AExp -> State -> _
(&&&) :: a -> a -> a
x &&& _ = x
-- {-@ lemma_asimp :: a:_ -> s:_ -> {aval (asimp a) s = aval a s} @-}
-- lemma_asimp (AVal _) _ = ()
-- lemma_asimp (AVar _) _ = ()
-- lemma_asimp (AAdd a1 a2) s = case (asimp a1, asimp a2) of
-- (AVal _, AVal _) -> lemma_asimp a1 s &&& lemma_asimp a2 s
-- (b1 , b2 ) -> lemma_asimp a1 s &&& lemma_asimp a2 s
Case Study: Optimizing Expressions
Lets define and "prove correct" some simple code transformations
Recall: State
data State
= Emp -- ^ Empty `State`
| Bind Var Val State -- ^ Extend `State` by assigning `Var` to `Val`
-- | Lookup the value of `x` in `s`
{-@ reflect get @-}
get :: Var -> State -> Val
get x (Bind y v s)
| x == y = v -- ^ found the variable `x`, return v
| otherwise = get x s -- ^ recursively search `s`
get x Emp = 0 -- ^ simplification
Recall: Arithmetic Expressions
type Var = String
type Val = Int
data AExp
= AVar Var -- ^ x
| AVal Val -- ^ v
| AAdd AExp AExp -- ^ e1 + e2
Recall: Evaluating Expressions in some State
{-@ reflect aval @-}
aval :: AExp -> State -> Val
aval (AVal v) _ = v
aval (AVar x) st = get x st
aval (AAdd e1 e2) st = (aval e1 st) + (aval e2 st)
Optimal Expressions
An expression is "optimal" if it has no sub-term AAdd n1 n2
Recursively normalize subexpressions and then Step 2
Step 1: Add a Constant to an NAExp
{-@ reflect cplus @-}
{-@ cplus :: Int -> NAExp -> NAExp @-}
cplus n (AAdd a1 a2) = AAdd a1 (cplus n a2)
cplus n (AVar x) = AAdd (AVar x) (AVal n)
cplus n (AVal m) = AVal (n + m)
Step 1: Why is it "correct"?
Proof follows structure of lemma_cplus
{-@ lemma_cplus :: n:Int -> a:NAExp -> s:_ ->
{aval (cplus n a) s = n + aval a s}
@-}
lemma_cplus n (AAdd a1 a2) s = lemma_cplus n a2 s
lemma_cplus n (AVar _) _ = ()
lemma_cplus n (AVal _) _ = ()
Step 2: Add an NAExp to another NAExp
This is the tricky one
{-@ reflect nplus @-}
{-@ nplus :: NAExp -> NAExp -> NAExp @-}
nplus (AAdd a1 a2) b = AAdd a1 (nplus a2 b)
nplus (AVal n) b = cplus n b
nplus (AVar x) b = AAdd (AVar x) b
Step 2: Why is it "correct"?
Proof follows structure of lemma_nplus
{-@ lemma_nplus :: a:NAExp -> b:NAExp -> s:_ ->
{aval (nplus a b) s = aval a s + aval b s}
@-}
lemma_nplus (AAdd a1 a2) b s = lemma_nplus a2 b s
lemma_nplus (AVal n) b s = lemma_cplus n b s
lemma_nplus (AVar x) b s = ()