240 likes | 360 Vues
This talk explores the creation of an abstract machine that handles exceptions in a small programming language. Key techniques include defunctionalization, originally introduced by John Reynolds in 1972 and revitalized by Olivier Danvy and others. The presentation outlines how to manipulate continuations and define new semantics, making the evaluation order explicit. Through systematic refactoring, we reveal the underlying abstract machine architecture, showcasing methods like marking/unmarking and unwinding the control stack for efficient computation. The discussion also touches on potential further work involving monads and compiler calculations.
E N D
Exception • An event that causes a computation to terminate in a non-standard way. Abstract Machine • A term-rewriting system for executing programs in a particular language.
This Talk • We show how to calculate an abstract machine for a small language with exceptions; • The key technique is defunctionalization, first introduced by John Reynolds in 1972; • Somewhat neglected in recent years, but now re-popularised by Olivier Danvy et al.
Arithmetic Expressions Syntax: data Expr = Val Int | Add Expr Expr Semantics: eval :: Expr Int eval (Val n) = n eval (Add x y) = eval x + eval y
Step 1 - Add Continuations Make the evaluation order explicit, by rewriting the semantics in continuation-passing style. Definition: A continuation is a function that is applied to the result of another computation.
eval x + eval y computation continuation Example: Basic idea: Generalise the semantics to make the use of continuations explicit.
Aim: define a new semantics eval’ :: Expr (Int Int) Int such that eval’ e c = c (eval e) and hence eval e = eval’ e (n n)
= c (eval (Add x y)) = c (eval x + eval y) = (n c (n + eval y)) (eval x) = eval’ x (n c (n + eval y)) = eval’ x (n (m c (n+m)) (eval y)) = eval’ x (n eval’ y (m c (n+m)) Case: e = Add x y eval’ (Add x y) c c (eval (Add x y)) c (eval x + eval y) (n c (n + eval y)) (eval x) eval’ x (n c (n + eval y)) eval’ x (n (m c (n+m)) (eval y))
New semantics: eval’ :: Expr Cont Int eval’ (Val n) c = c n eval’ (Add x y) c = eval’ x (n eval’ y (m c (n+m))) The evaluation order is now explicit.
Step 2 - Defunctionalize Make the semantics first-order again, by rewriting eval’ using the defunctionalization technique. Basic idea: Represent the continuations we actually need using a datatype.
Continuations: eval :: Expr Int eval e = eval’ e (n n) (n n) eval’ :: Expr Cont Int eval’ (Val n) c = c n eval’ (Add x y) c = eval’ x (n eval’ y (m c (n+m))) (n eval’ y (m c (n+m))) (m c (n+m))
Combinators: c1 :: Cont c1 = n n c2 :: Expr Cont Cont c2 y c = n eval’ y (c3 n c) c3 :: Int Cont Cont c3 n c = m c (n+m)
Datatype: data CONT = C1 | C2 Expr CONT | C3 Int CONT Semantics: apply :: CONT Cont apply C1 = c1 apply (C2 y c) = c2 y (apply c) apply (C3 n c) = c3 n (apply c)
Aim: define a function eval’’ :: Expr CONT Int such that eval’’ e c = eval’ e (apply c) and hence eval e = eval’’ e C1
By calculation, we obtain: eval’’ (Val n) c = apply c n eval’’ (Add x y) c = eval’’ x (C2 y c) apply C1 n = n apply (C2 y c) n = eval’’ y (C3 n c) apply (C3 n c) m = apply c (n+m) The semantics is now first-order again.
Step 3 - Refactor Question: • What have we actually produced? Answer: • An abstract machine, but this only becomes clear after we refactor the components.
Abstract machine: data Cont = STOP | EVAL Expr Cont | ADD Int Cont run e = eval e STOP eval (Val n) c = exec c n eval (Add x y) c = eval x (EVAL y c) exec STOP n = n exec (EVAL y c) n = eval y (ADD n c) exec (ADD n c) m = exec c (n+m)
= eval (Add (Val 1) (Val 2)) STOP = eval (Val 1) (EVAL (Val 2) STOP) = exec (EVAL (Val 2) STOP) 1 = eval (Val 2) (ADD 1 STOP) = exec (ADD 1 STOP) 2 = exec 3 STOP = 3 Example: run (Add (Val 1) (Val 2))
Adding Exceptions Syntax: data Expr = ••• | Throw | Catch Expr Expr Semantics: eval :: Expr Maybe Int eval (Val n) = Just n eval (Throw) = Nothing eval (Add x y) = eval xeval y eval (Catch x y) = eval x eval y
Step 1 - Add Continuations Make evaluation order explicit. Step 2 - Defunctionalize Make first-order once again. Step 3 - Refactor Reveal the abstract machine.
Control stack: data Cont = STOP | EVAL Expr Cont | ADD Int Cont | HAND Expr Cont Evaluating an expression: eval :: Expr Cont Maybe Int eval (Val n) c = exec c n eval (Throw) c = unwind c eval (Add x y) c = eval x (EVAL y c) eval (Catch x y) c = eval x (HAND y c)
Executing the control stack: exec :: Cont Int Maybe Int exec STOP n = Just n exec (EVAL y c) n = eval y (ADD n c) exec (ADD n c) m = exec c (n+m) exec (HAND _ c) n = exec c n Unwinding the control stack: unwind :: Cont Maybe Int unwind STOP = Nothing unwind (EVAL _ c) = unwind c unwind (ADD _ c) = unwind c unwind (HAND y c) = eval y c
Summary • Purely calculational development of an abstract machine for a language with exceptions; • Key ideas of marking/unmarking and unwinding the stack arise directly from the calculations; • Techniques have been used to systematically design many other machines - Danvy et al.
Further Work • Exploiting monads and folds; • Reasoning about efficiency; • Generalising the language; • Calculating a compiler.