450 likes | 600 Views
CS5205: Foundations in Programming Languages. Interpreters. Interpreters. Compilers generate low-level code for execution Interpreters operate over abstract syntax of programs and would change the machine environment according to the commands of the programs. Advantage of Interpreters
E N D
CS5205: Foundations in Programming Languages Interpreters. Interpreters
Interpreters • Compilers generate low-level code for execution • Interpreters operate over abstract syntax of programs and would change the machine environment according to the commands of the programs. • Advantage of Interpreters • more compact • easier to extend/modify • easier to debug • good for implementing small languages • Disadvantage : overheads of interpretation Interpreters
Topics • Monadic Interpreter • Error messages with positions • States • Output • Non-determinism • Call-by-Value vs Call by Name • CPS vs Monads Reference : The essence of functional programming. Phil Wadler invited talk at POPL 1992 Interpreters
Simple Language • Abstract syntax tree data Term = Var Name | Con Int | Add Term Term | Lam Name Term | App Term Term type Name = String • Lambda calculus plus integer and addition Interpreters
Environment type Env = [(Name,Value)] • Interpreter interp :: Term -> Env -> Value Simple Interpreter • Value of Language data Value = Wrong | Num Int | Fun (Value -> Value) Term Value interp Env Interpreters
Displaying a given value instance Show Value where show Wrong = "<wrong>" show (Num i) = show i show (Fun f) = "<function>" Direct-Style Interpreter • Looking up environment lookup :: Name -> Environment -> Value lookup x [] = Wrong lookup x ((y,b):r) = if x==y then b else lookup x r Interpreters
Direct-Style Interpreter • Main interpreter interp :: Term -> Environment -> Value interp (Var x) e = lookup x e interp (Con i) e = (Num i) interp (Add u v) e = let a = interp u e b = interp v e in (add a b) interp (Lam x v) e = (Fun (\a -> interp v ((x,a):e))) interp (App u v) e = let f = interp u e b = interp v e in (apply f b) Interpreters
Applying a function to its argument apply :: Value -> Value -> Value apply (Fun k) a = k a apply _ _ = Wrong Direct-Style Interpreter • Adding two numbers add :: Value -> Value -> Value add (Num i) (Num j) = (Num (i+j)) add _ _ = Wrong Interpreters
Some Challenges • Direct-style interpreter easier to write. However, it may be difficult to add new features. Example: • What if we wish to add more meaningful error messages? • What if we wish to count the number of operations that are performed by the interpreter? • What if we wish to implement call-by-name interpretation? Interpreters
that satisfies three laws: i :o x = x x :o i = x x :o (y :o z) = (x :o y) :o z Monoids vs Monads • Recall that monoid M is a triple: ( M, i :: M, :o :: M -> M -> M ) Interpreters
that satisfy three laws: (return a) >>= k = k a m >>= return = m m >>= (\a -> (k a) >>= (\b -> h b)) = (m >>= (\a -> k a)) >>= (\b -> h b) Monoids vs Monads • Similarly, monad M is a more complex triple: ( M, return :: a -> M a, >>= :: M a -> (a -> M b) -> M b ) Interpreters
Monadic Interpreter • We can implement our interpreter using monadic style. interp :: Term -> Environment -> M Value interp (Var x) e = lookup x e interp (Con i) e = return (Num i) interp (Add u v) e = do a <- interp u e b <- interp v e (add a b) interp (Lam x v) e = return (Fun (\a -> interp v ((x,a):e))) interp (App u v) e = do f <- interp u e b <- interp v e (apply f b) Interpreters
Modified value type data Value = Wrong | Num Int | Fun (Value -> M Value) Monadic Interpreter • Looking up environment lookup :: Name -> Environment -> M Value lookup x [] = return Wrong lookup x ((y,b):r) = if x==y then return b else lookup x r Interpreters
Applying a function to its argument apply :: Value -> Value -> M Value apply (Fun k) a = k a apply _ _ = return Wrong Monadic Interpreter • Adding two numbers add :: Value -> Value -> M Value add (Num i) (Num j) = return (Num (i+j)) add _ _ = return Wrong Interpreters
Using an isomorphic data declaration : data M t = Id t instance Monad M where return a = Id a (Id m) >>= k = (k m) Monadic Interpreter • Identity Monad type M t = t instance Monad M where return a = a m >>= k = (k m) Interpreters
Adding Error Message • To provide more meaningful error messages, we can use a new monad. data E t = Success t | Error String type M t = E t instance Monad M where return a = Success a (Success v) >>= k = k v (Error s) >>= k = (Error s) errorM :: String -> M t errorM s = Error s Interpreters
Applying a function to its argument apply :: Value -> Value -> M Value apply (Fun k) a = k a apply f _ = errorM “Argument “ ++show f++” must be a function” Change Wrong to Error Messages • Adding two numbers add :: Value -> Value -> M Value add (Num i) (Num j) = return (Num (i+j)) add a b = errorM “Adding “++ show i++show j ++ “:should be numbers!” Interpreters
Modified value type data Value = Num Int | Fun (Value -> M Value) Change Wrong to Error Messages • Looking up environment lookup :: Name -> Environment -> M Value lookup x [] = errorM “Unknown Var” ++ show x lookup x ((y,b):r) = if x==y then return b else lookup x r Interpreters
Adding Error Message with Position • To provide more meaningful error messages, we can use a new monad. data E t = Success t | Error String type M t = Position -> E t type Position = Int instance Monad M where return a = (\p -> return a) m >>= k = (\p -> (m p) >>= (\x -> k x p)) errorM :: String -> M t errorM s = \ p -> Error ("Line "++show p ++ ": “++ s)) Interpreters
Key changes to Interpreter interp :: Term -> Environment -> M Value interp (At p t) e = resetP p (interp t e) resetP :: Position -> M a -> M a resetP q m = (\p -> (m q)) Language with Position • Abstract syntax tree data Term = Var Name | Con Int | Add Term Term | Lam Name Term | App Term Term | At Position Term Interpreters
Requires a state monad, captured by : type M t = State -> (State,t) type State = Int instance Monad M where return a = (\s -> (s,a)) m >>= k = (\s -> let (s1,v1) = m s in k v1 s1) Interpreter with States • Assume we wish to keep track of the number of additions and applications performed by a given interpreter. Interpreters
Update state by: tickS :: M () tickS = \s -> (s+1,()) clearS :: M () clearS = \s -> (0,()) Interpreter with States • Read state by: getState :: M State getState = \s -> (s,s) Interpreters
Changes to the State Interpreter interp :: Term -> Environment -> M Value interp (Add u v) e = do a <- interp u e b <- interp v e tickS add a b) interp (App u v) e = do f <- interp u e b <- interp v e tickS (apply f b) Interpreters
Topics • Monadic Interpreter • Error messages with positions • States • Output • Non-determinism • Call-by-Value vs Call by Name • CPS vs Monads Interpreters
Interpreter with Output • May like commands to write output to terminal • Solution : type M t = (String, t) instance Monad M where return a = (“”,a) m >>= k = let (r, a)=m (s, b)=k a in (r++s, b) instance Show (M t) where show (s,a) = “Output : “++s++ ” Value: “++show a Interpreters
Key changes to Interpreter interp :: Term -> Environment -> M Value interp (Out t) e = do v <- (interp t e) outM v return v outM :: Value -> M () outM v = (show v++” ;”, ()) Language with Output • Abstract syntax tree data Term = Var Name | … | Out Term Interpreters
An example Add (Con 3) (Amb (Con 1) (Con 2)) This adds 3 to a choice of either 1 or 2, giving either 4 or 5 as its set of possible outcomes. Non-Deterministic Language • We may have a non-determinstic operator data Term = Var Name | … | Fail | Amb Term Term Interpreters
Non-Deterministic Monad • Solution : type M t = [t] instance Monad M where return a = [a] m >>= k = [b | a <- m, b <- k a] zeroM = [] plusM m k = m ++ k Interpreters
Changes to Interpreter • Key changes to Interpreter interp :: Term -> Environment -> M Value interp Fail e = zeroM interp (Amb a b) e = interp a e `plusM` interp b e Interpreters
Topics • Monadic Interpreter • Error messages with positions • States • Output • Non-determinism • Call-by-Value vs Call by Name • CPS vs Monads Interpreters
To make into call-by-name interpreter, change : interp (App u v) e = do f <- interp u e (apply f (interp v e) ) type Environment = [(Name, M Value)] data Value = … | Fun (M Value -> M Value) Call-by-Name Interpreter • Existing interpreter is call-by-value since the argument is evaluated before entering the function body. interp (Lam x v) e = return (Fun (\a -> interp v ((x,a):e))) interp (App u v) e = do f <- interp u e b <- interp v e (apply f b) Interpreters
Call-by-Name Interpreter • Call-by-name interpretation terminates more often . • An example is: t = (Lam “x” (App (Var “x”) (Var “x”))) loop = App t t constfn = Lam “x” (Con 13) expr = App constfn loop Interpreters
Doubling a number would be: double :: Int -> (Int -> Cont) -> Cont double x c = add x x c Continuation-Passing Style (CPS) • CPS style requires each function to carry a ‘continuation’ which must be applied to the result of the function. Each continuation is like a goto method. • In CPS style, the addition method would be: add :: Int -> Int -> (Int -> Cont) -> Cont add x y c = c (x+y) sub,mult :: Int -> Int -> (Int -> Cont) -> Cont eq :: Int -> Int -> (Bool -> Cont) -> Cont Interpreters
More religiously: fact :: Int -> (Int -> Cont) -> Cont fact n c = eq n 0 (\b -> if b then c 1 else sub n 1 (\m -> fact m (\ r -> mult n r c))) Continuation-Passing Style (CPS) • How about CPS-style for factorial. fact :: Int -> (Int -> Cont) -> Cont fact n c = if n==0 then c 1 else fact (n-1) (\ r -> c (n*r)) Interpreters
Continuation-Passing Style (CPS) • Advantages: • tail-recursive code • better modularity for easier code changes • Disadvantage • harder to read (higher-order) Interpreters
CPS Monad • Solution : type M t = (t -> Cont) -> Cont instance Monad M where return a = \c -> c a m >>= k = \c -> m (\a -> k a c) show :: M Value -> String show m = let v=(m (\x -> x)) in show v • All the rest are unchanged. Interpreters
CPS Interpreter • If we expand CPS monad, we get a CPS interpreter! interp :: Term -> Environment -> (Value -> Cont) -> Cont interp (Var x) e = \ c -> lookup x e c interp (Con i) e = \ c -> c (Num i) interp (Add u v) e = \ c -> inter u e (\ a -> interp v e (\ b -> add a b c)) interp (Lam x v) e = \ c -> c (Fun (\a -> interp v (x:a):e )) interp (App u v) e = \ c -> interp u e (\ f -> interp v e (\ b -> apply f b c)) Interpreters
An example is below where cc is the captured current continuation call/cc (\ cc -> body ) Call with Current Continuation • This is a feature in Scheme that allows a current continuation to be captured or reified. call/cc h where h is a function that takes the current continuation as a parameter. Interpreters
Captured continuation : \n -> Add 1 n • Continuation when r was applied to 6 : • \n -> Add 2 n • Final Outcome : Add 1 6 7 A Simple Example • An example in our tiny language Add 1 (Call/cc r (Add 2 (r 6))) Interpreters
Breaking from a Loop • A useful command is to break out of a loop: search :: (a->Bool) -> [a] -> Maybe a search wanted xs = let _ = foreach (\ element -> if (wanted element) then return (Some element) else () ) xs in None • The foreach loop iterates over the list and would return the first element found in xs list, or None otherwise : Interpreters
Breaking from a Loop with call/cc • Using call/cc to capture a current context search :: (a->Bool) -> [a] -> Maybe a search wanted xs = call/cc (\ ret -> let _ = foreach (\ element -> if (wanted element) then ret (Some element) else () ) xs in None ) • When captured continuation is applied, the current continuation is lost! Interpreters
To add call/cc to the interpreted language: data Term = … | Callcc Name Term interp (Callcc x v) e = callccK (\k -> interp v ((x,Fun k):e)) Interpreter with call/cc • Once interpreter is in CPS style, it is quite easy to provide a call/cc functionality using: callccK :: ((a -> K b) -> K a) -> K a callccK h = \ c -> let k a = \c2 -> c a in h k c Interpreters
Monads in CPS • To achieve the effect of a monad M in CPS, define: type Cont = M Value showK n = showM (n return) n :: K Value return :: Value -> M Value • CPS interpreter can support modular changes in a similar way as monadic interpreters. Interpreters
With CPS, one writes: \ c -> m (\a -> k a c) Monads versus CPS • With monads, we write : m >>= (\a -> k a) • Almost equal expressive power, except that monad can be made into an ADT but not continuation. Interpreters
Invoking out of scope: return 22 23 Exit Continuation (uses ref type) • Current continuation can escape call/cc let return = ref _ (+ 1 (call/cc (\ cont -> return := cont 1))) 2 Interpreters