{-@ LIQUID "--no-termination" @-}
{-@ LIQUID "--short-names" @-}
{-@ LIQUID "--prune-unsorted" @-}
module Eval (Map, Expr (..), plus, eval, topEval, safeEval) where
import Prelude hiding (lookup)
import qualified Data.Set as S
{-@ impossible :: {v: _ | false} -> a @-}
impossible :: String -> a
impossible = error
type Env = Map Var Expr
plus :: Expr -> Expr -> Expr
topEval :: Expr -> Expr
safeEval :: Map Var Expr -> Expr -> Maybe Expr
--------------------------------------------------------
-- | Membership test (SKIP?)
--------------------------------------------------------
{-@ member :: (Eq k) => k:_ -> m:_ -> {v:Bool | v <=> has k m} @-}
member :: (Eq k) => k -> Map k v -> Bool
member k' (Bind k _ m)
| k' == k = True
| otherwise = member k' m
member _ Emp = False
Case Study: Associative Maps & Evaluation
Associative Maps
We've all been bitten by these!
ghci> :m +Data.Map
ghci> let m = fromList [ ("haskell" , "lazy")
, ("javascript", "eager")]
ghci> m ! "haskell"
"lazy"
ghci> m ! "python"
"*** Exception: key is not in the map
Associative Maps & Evaluation
Next, lets see how to use:
Sets to create safe associative maps
Measures to create well-scoped evaluators
Associative Maps
A Simple Associative Map
Lets start with the type definition:
data Map k v = Emp
| Bind k v (Map k v)
deriving (Eq, Ord, Show)
Lets Banish Missing Key Exceptions!
1. Refine the Type with defined keys
keys :: Map k v -> Set k
type MapK k v Ks = {m:Map k v | keys m = Ks}
2. Refine the API to track keys
empty :: MapK k v emp
insert :: k:k -> v -> m:Map k v -> MapK k v {add k m}
lookup :: k:k -> {m: Map | has k m} -> v
Specifying the Set of keys
Via a measure similar to elems of a List:
{-@ measure keys @-}
keys :: (Ord k) => Map k v -> S.Set k
keys Emp = S.empty
keys (Bind k _ m) = add k m
The Empty Map
The empty map has no keys
{-@ empty :: {m:Map k v | noKeys m} @-}
empty :: Map k v
empty = Emp
{-@ inline noKeys @-}
noKeys :: (Ord k) => Map k v -> Bool
noKeys m = keys m == S.empty
Inserting a New Key-Value Binding
Adds the key to those of the old Map
{-@ insert :: k:_ -> _ -> m:_ -> {v: _ | keys v = add k m } @-}
insert :: k -> v -> Map k v -> Map k v
insert k v m = Bind k v m
{-@ inline add @-}
add :: (Ord k) => k -> Map k v -> S.Set k
add k kvs = S.union (S.singleton k) (keys kvs)
Exercise: Looking Up a Key's Value
Q: Urgh! How can we prevent the impossible from happening?
{-@ lookup :: (Eq k) => k:k -> {m:Map k v | has k m} -> v @-}
lookup k' (Bind k v m)
| k' == k = v
| otherwise = lookup k' m
lookup _ Emp = impossible "lookup"
{-@ inline has @-}
has :: (Ord k) => k -> Map k v -> Bool
has k m = True -- EXERCISE fix using,
-- keys :: Map k v -> Set k
-- S.member :: k -> S.Set k -> Bool
Key Not Found Begone!
Errors caught at compile time!
test = [ lookup hs langs -- Ok
, lookup py langs -- Err
]
where
langs = Bind hs "lazy" $
Bind js "eager" $
Emp
hs = "haskell"
js = "javascript"
py = "python"
Well-Scoped Evaluators
Expressions
Lets define a small language of Expr
data Var = V String deriving (Eq, Ord, Show)
data Expr = Val Int
| Var Var
| Plus Expr Expr
| Let Var Expr Expr
plus (Val i) (Val j) = Val (i+j)
plus _ _ = impossible "plus"
Environments
An Environment maps Variables to Values
{-@ type Env = Map Var Val @-}
Evaluate Using Environments
We can now eval an Expr as:
{-@ eval :: Env -> Expr -> Val @-}
eval _ i@(Val _) = i
eval g (Var x) = lookup x g
eval g (Plus e1 e2) = plus (eval g e1) (eval g e2)
eval g (Let x e1 e2) = eval gx e2
where
gx = insert x v1 g
v1 = eval g e1
Yikes! lookup is rejected! How to ensure that x::Var is in g::Env?