07.01.2014 Views

How do we get a stack trace in a lazy functional language? - Haskell

How do we get a stack trace in a lazy functional language? - Haskell

How do we get a stack trace in a lazy functional language? - Haskell

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Why can’t I <strong>get</strong> a <strong>stack</strong> <strong>trace</strong>?<br />

Simon Marlow


Motivation


Background<br />

• A <strong>stack</strong> <strong>trace</strong> (or lexical call context) conta<strong>in</strong>s<br />

a lot of <strong>in</strong>formation, often enough to<br />

diagnose a bug.<br />

• In an imperative <strong>language</strong>, where every<br />

function call pushes a <strong>stack</strong> frame, the<br />

execution <strong>stack</strong> conta<strong>in</strong>s enough <strong>in</strong>formation<br />

to reconstruct the lexical call context.<br />

• The same isn’t true <strong>in</strong> <strong>Haskell</strong>, for various<br />

reasons...


1. Tail Call Optimisation<br />

– TCO means that important <strong>in</strong>formation about the<br />

call cha<strong>in</strong> is not reta<strong>in</strong>ed on the <strong>stack</strong><br />

– But TCO is essential, <strong>we</strong> can’t just turn it off<br />

ma<strong>in</strong> = <strong>do</strong><br />

[x] Int<br />

f x = g (x-1)<br />

g :: Int -> Int<br />

g x = 100 `div` x


2. Lazy evaluation<br />

– Lazy evaluation results <strong>in</strong> an execution <strong>stack</strong> that<br />

looks noth<strong>in</strong>g like the lexical call <strong>stack</strong>.<br />

– When a computation is suspended (a thunk) <strong>we</strong><br />

should capture the call <strong>stack</strong> and store it with the<br />

thunk.<br />

ma<strong>in</strong> = <strong>do</strong><br />

[x] Int<br />

g x = 100 `div` x


3. Transformation and optimisation<br />

– <strong>we</strong> <strong>do</strong> not want the transformations <strong>do</strong>ne by<br />

GHC’s optimiser to lose <strong>in</strong>formation or mangle<br />

the call <strong>stack</strong>.<br />

– <strong>we</strong>’ve already established that strictness analysis<br />

should not distort the <strong>stack</strong>.<br />

– But even <strong>in</strong>l<strong>in</strong><strong>in</strong>g a function will lose <strong>in</strong>formation<br />

if <strong>we</strong> aren’t careful.


4. Even if <strong>we</strong> fix 1—3, high-level abstractions<br />

like monads result <strong>in</strong> strange <strong>stack</strong>s<br />

– examples com<strong>in</strong>g...<br />

• We need a framework for th<strong>in</strong>k<strong>in</strong>g about the<br />

issues.


A construct for push<strong>in</strong>g on the <strong>stack</strong><br />

push L E<br />

• “push label L on the <strong>stack</strong> while evaluat<strong>in</strong>g E”<br />

• this is a construct of the source <strong>language</strong> and the<br />

<strong>in</strong>termediate <strong>language</strong> (Core)<br />

• Compiler can add these automatically, or the user can add<br />

them<br />

• Th<strong>in</strong>k {-# SCC .. #-} <strong>in</strong> GHC<br />

• We <strong>get</strong> to choose how detailed <strong>we</strong> want to be:<br />

– exported functions only<br />

– top-level functions only<br />

– all functions (good for profil<strong>in</strong>g)<br />

– call sites (good for debugg<strong>in</strong>g)<br />

– all sub-expressions (f<strong>in</strong>e-gra<strong>in</strong>ed debugg<strong>in</strong>g or profil<strong>in</strong>g)


• Def<strong>in</strong>e <strong>stack</strong>s:<br />

type Stack = [Label]<br />

push :: Label -> Stack -> Stack<br />

call :: Stack -> Stack -> Stack


• Def<strong>in</strong>e <strong>stack</strong>s:<br />

type Stack = [Label]<br />

push :: Label -> Stack -> Stack<br />

call :: Stack -> Stack -> Stack<br />

<strong>stack</strong> at the call site


• Def<strong>in</strong>e <strong>stack</strong>s:<br />

type Stack = [Label]<br />

push :: Label -> Stack -> Stack<br />

call :: Stack -> Stack -> Stack<br />

<strong>stack</strong> at the call site<br />

<strong>stack</strong> of the<br />

function


• Def<strong>in</strong>e <strong>stack</strong>s:<br />

type Stack = [Label]<br />

push :: Label -> Stack -> Stack<br />

call :: Stack -> Stack -> Stack<br />

<strong>stack</strong> at the call site<br />

<strong>stack</strong> of the<br />

function<br />

<strong>stack</strong> for the call


Executable semantics<br />

eval :: Stack -> Expr -> E (Stack,Expr)<br />

eval stk (EInt i) = return (stk, EInt i)<br />

eval stk (ELam x e) = return (stk, ELam x e)<br />

eval stk (EPush l e) = eval (push l stk) e<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

eval stk (EApp f x) = <strong>do</strong><br />

(lam_stk, ELam y e)


Executable semantics<br />

current <strong>stack</strong><br />

eval :: Stack -> Expr -> E (Stack,Expr)<br />

eval stk (EInt i) = return (stk, EInt i)<br />

eval stk (ELam x e) = return (stk, ELam x e)<br />

eval stk (EPush l e) = eval (push l stk) e<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

eval stk (EApp f x) = <strong>do</strong><br />

(lam_stk, ELam y e)


Executable semantics<br />

eval :: Stack -> Expr -> E (Stack,Expr)<br />

eval stk (EInt i) = return (stk, EInt i) E is a State monad<br />

eval stk (ELam x e) = return (stk, ELam x conta<strong>in</strong><strong>in</strong>g e) the Heap:<br />

a mapp<strong>in</strong>g from Var<br />

eval stk (EPush l e) = eval (push l stk) e to (Stack,Expr)<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

eval stk (EApp f x) = <strong>do</strong><br />

(lam_stk, ELam y e)


Executable semantics<br />

eval :: Stack -> Expr -> E (Stack,Expr)<br />

eval stk (EInt i) = return (stk, EInt i)<br />

eval stk (ELam x e) = return (stk, ELam x e)<br />

Values are<br />

straightforward<br />

eval stk (EPush l e) = eval (push l stk) e<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

eval stk (EApp f x) = <strong>do</strong><br />

(lam_stk, ELam y e)


Executable semantics<br />

eval :: Stack -> Expr -> E (Stack,Expr)<br />

eval stk (EInt i) = return (stk, EInt i)<br />

eval stk (ELam x e) = return (stk, ELam x e)<br />

eval stk (EPush l e) = eval (push l stk) e<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

eval stk (EApp f x) = <strong>do</strong><br />

(lam_stk, ELam y e)


Executable semantics<br />

eval :: Stack -> Expr -> E (Stack,Expr)<br />

eval stk (EInt i) = return (stk, EInt i)<br />

eval stk (ELam x e) = return (stk, ELam x e)<br />

eval stk (EPush l e) = eval (push l stk) e<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

suspend the<br />

eval stk (EApp f x) = <strong>do</strong><br />

computation e1 on the<br />

(lam_stk, ELam y e)


Executable semantics<br />

eval :: Stack -> Expr -> E (Stack,Expr)<br />

eval stk (EInt i) = return (stk, EInt i)<br />

eval stk (ELam x e) = return (stk, ELam x e)<br />

eval stk (EPush l e) = eval (push l stk) e<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

eval stk (EApp f x) = <strong>do</strong><br />

(lam_stk, ELam y e)


Executable semantics (variables)<br />

eval stk (EVar x) = <strong>do</strong><br />

r <strong>do</strong><br />

deleteHeap x<br />

(stkv, v)


Given this semantics, def<strong>in</strong>e push & call<br />

• The problem now is to f<strong>in</strong>d suitable def<strong>in</strong>itions<br />

of push and call that<br />

– Behave like a call <strong>stack</strong><br />

– Have nice properties:<br />

• transformation-friendly<br />

• predictable/robust<br />

• implementable


Lazy evaluation is dealt with<br />

• Lazy evaluation is dealt<br />

with by<br />

– captur<strong>in</strong>g the current<br />

<strong>stack</strong> when <strong>we</strong> suspend a<br />

computation as a thunk<br />

<strong>in</strong> the heap<br />

– temporarily restor<strong>in</strong>g the<br />

<strong>stack</strong> when the thunk is<br />

evaluated<br />

• Noth<strong>in</strong>g controversial at<br />

all – <strong>we</strong> just need a<br />

mechanism for captur<strong>in</strong>g<br />

and restor<strong>in</strong>g the <strong>stack</strong>.<br />

eval stk (ELet (x,e1) e2) = <strong>do</strong><br />

<strong>in</strong>sertHeap x (stk,e1)<br />

eval stk e2<br />

eval stk (EVar x) = <strong>do</strong><br />

r


Tail calls are dealt with<br />

• The semantics says noth<strong>in</strong>g about tail calls –<br />

push always pushes on the <strong>stack</strong>.<br />

• Even if the underly<strong>in</strong>g execution model is<br />

<strong>do</strong><strong>in</strong>g TCO, the call <strong>stack</strong> simulation must not.


Examples<br />

f = λ x. push “f” x+x<br />

ma<strong>in</strong> = λ x. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong> f y<br />

• The heap is <strong>in</strong>itialised with the top-level b<strong>in</strong>d<strong>in</strong>gs (give<br />

each the <strong>stack</strong> )<br />

• When <strong>we</strong> <strong>get</strong> to (f y), current <strong>stack</strong> is <br />

• f is already evaluated<br />

• call = <br />

• eval (push f y+y)<br />

• eval (y+y)<br />

• at the +, the current <strong>stack</strong> is <br />

Let’s assume, for now,<br />

call Sapp Slam = Sapp


Use the call-site <strong>stack</strong>?<br />

call sapp slam = sapp<br />

• Previous example suggests this might be a<br />

good choice?<br />

• After all, this gives exactly the call <strong>stack</strong> you<br />

would <strong>get</strong> <strong>in</strong> a strict <strong>language</strong>


But <strong>we</strong> have to be careful<br />

• If <strong>in</strong>stead of this:<br />

f = λx. push “f” x+x<br />

ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong> f y<br />

• We wrote this:<br />

f = push “f” (λx . x+x)<br />

ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong> f y<br />

• Now it <strong>do</strong>esn’t work so <strong>we</strong>ll: the “f” label is lost.<br />

• In this semantics, the scope of push <strong>do</strong>es not<br />

extend <strong>in</strong>to lambdas


Just label all the lambdas?<br />

• Idea: make the compiler label all the lambdas<br />

automatically<br />

• e.g. the compiler <strong>in</strong>serts a push <strong>in</strong>side any<br />

lambda:<br />

f = push “f” (λx . push “f1” x+x)<br />

ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong> f y<br />

• Now <strong>we</strong> <strong>get</strong> a useful <strong>stack</strong> aga<strong>in</strong>:


Some properties<br />

• Add<strong>in</strong>g an extra b<strong>in</strong>d<strong>in</strong>g <strong>do</strong>esn’t change the<br />

<strong>stack</strong><br />

f = push “f” (λ x . push “f1” x+x)<br />

g = push “g” f<br />

ma<strong>in</strong> = λ x. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong> g y<br />

• In this semantics ‘push L x == x’<br />

• arguably useful: the <strong>stack</strong> is robust with<br />

respect to this transformation (by the<br />

compiler or user)


But...<br />

• eta-expansion changes the <strong>stack</strong><br />

f = push “f” (λ x . push “f1” x+x)<br />

g = λ x . push “g” f x<br />

ma<strong>in</strong> = λ x. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong> g y<br />

• Now the <strong>stack</strong> at the + will be


Concrete example<br />

• When <strong>we</strong> tried this for real, <strong>we</strong> found that <strong>in</strong><br />

functions like<br />

h = f . g<br />

• h <strong>do</strong>es not appear on the <strong>stack</strong>, although <strong>in</strong><br />

h x = (f . g) x<br />

• now it <strong>do</strong>es. This is surpris<strong>in</strong>g and<br />

undesirable.


Worse...<br />

• Let’s make a state monad:<br />

newtype M s a = M { unM :: s -> (s,a) }<br />

<strong>in</strong>stance Monad (M s) where<br />

(M m) >>= k = M $ λ s -> case m s of<br />

(s',a) -> unM (k a) s'<br />

return a = M $ λ s -> (s,a)<br />

errorM :: Str<strong>in</strong>g -> M s a<br />

errorM s = M $ λ _ -> error s<br />

runM :: M s a -> s -> a<br />

runM (M m) s = case m s of (_,a) -> a<br />

Suppose <strong>we</strong> want the<br />

<strong>stack</strong> when error is<br />

called, for debugg<strong>in</strong>g


Us<strong>in</strong>g a monad<br />

• Simple example:<br />

ma<strong>in</strong> = pr<strong>in</strong>t (runM (bar ["a","b"]) "state")<br />

bar :: [Str<strong>in</strong>g] -> M s [Str<strong>in</strong>g]<br />

bar xs = mapM foo xs<br />

foo :: Str<strong>in</strong>g -> M s Str<strong>in</strong>g<br />

foo x = errorM x


Us<strong>in</strong>g a monad<br />

• Simple example:<br />

ma<strong>in</strong> = pr<strong>in</strong>t (runM (bar ["a","b"]) "state")<br />

bar :: [Str<strong>in</strong>g] -> M s [Str<strong>in</strong>g]<br />

bar xs = mapM foo xs<br />

foo :: Str<strong>in</strong>g -> M s Str<strong>in</strong>g<br />

foo x = errorM x<br />

• We are look<strong>in</strong>g for a <strong>stack</strong> like<br />


Us<strong>in</strong>g a monad<br />

• Simple example:<br />

ma<strong>in</strong> = pr<strong>in</strong>t (runM (bar ["a","b"]) "state")<br />

bar :: [Str<strong>in</strong>g] -> M s [Str<strong>in</strong>g]<br />

bar xs = mapM foo xs<br />

foo :: Str<strong>in</strong>g -> M s Str<strong>in</strong>g<br />

foo x = errorM x<br />

• We are look<strong>in</strong>g for a <strong>stack</strong> like<br />

<br />

• Stack <strong>we</strong> <strong>get</strong>:


Why?<br />

• Take a typical monadic function:<br />

f = <strong>do</strong> p; q<br />

• Desurag<strong>in</strong>g gives<br />

f = p >> q<br />

• Add<strong>in</strong>g push:<br />

f = push “f” (p >> q)<br />

• Expand<strong>in</strong>g out (>>):<br />

f = push “f” (λ s -> case p s of (a,s’) -> b s’)<br />

• recall that push L (λ x . e) = λ x. e


The IO monad<br />

• In GHC the IO monad is def<strong>in</strong>ed like the state<br />

monad given earlier.<br />

• We found that with this <strong>stack</strong> semantics, <strong>we</strong><br />

<strong>get</strong> no useful <strong>stack</strong>s for IO monad code at all.<br />

• When profil<strong>in</strong>g, all the costs <strong>we</strong>re attributed<br />

to ma<strong>in</strong>.


call S app S lam = S app ?<br />

• We recovered the non-<strong>lazy</strong> non-TCO call <strong>stack</strong>,<br />

which is the <strong>stack</strong> you would <strong>get</strong> <strong>in</strong> a strict<br />

<strong>functional</strong> <strong>language</strong>.<br />

• But it isn’t good enough.<br />

– at least when used with monads or other highlevel<br />

<strong>functional</strong> abstractions


Can <strong>we</strong> f<strong>in</strong>d a better semantics?<br />

• call S app S lam = ?<br />

• non-starter: call S app S lam = S lam<br />

– ignores the call<strong>in</strong>g context<br />

– gives a purely lexical <strong>stack</strong>, not a call <strong>stack</strong><br />

– (possibly useful for flat profil<strong>in</strong>g though)<br />

• Clearly <strong>we</strong> want to take <strong>in</strong>to account both S app<br />

and S lam somehow.


The def<strong>in</strong>itions I want to use<br />

call Sapp Slam = Sapp ++ Slam’<br />

where (Spre, Sapp’, Slam’) = commonPrefix Sapp Slam<br />

push l s | l `elem` s = dropWhile (/= l) s<br />

| otherwise = l : s<br />

• Behaves nicely with <strong>in</strong>l<strong>in</strong><strong>in</strong>g:<br />

– “common prefix” is <strong>in</strong>tended to capture the call<br />

<strong>stack</strong> up to the po<strong>in</strong>t where the function was<br />

def<strong>in</strong>ed<br />

• useful for profil<strong>in</strong>g/debugg<strong>in</strong>g: the top-of<strong>stack</strong><br />

label is always correct, <strong>we</strong> just truncate<br />

the <strong>stack</strong> on recursion.


Status<br />

• GHC 7.4.1 has a new implementation of<br />

profil<strong>in</strong>g us<strong>in</strong>g push<br />

• +RTS –xc pr<strong>in</strong>ts the call <strong>stack</strong> when an<br />

exception is raised<br />

• Programmatic access to the call <strong>stack</strong>:


Status<br />

• GHC 7.4.1 has a new implementation of<br />

profil<strong>in</strong>g us<strong>in</strong>g push<br />

• +RTS –xc pr<strong>in</strong>ts the call <strong>stack</strong> when an<br />

exception is raised<br />

• Programmatic access to the call <strong>stack</strong>:<br />

-- | like '<strong>trace</strong>', but additionally pr<strong>in</strong>ts a call<br />

-- <strong>stack</strong> if one is available.<br />

<strong>trace</strong>Stack :: Str<strong>in</strong>g -> a -> a<br />

-- | like ‘error’, but <strong>in</strong>cludes a call <strong>stack</strong><br />

errorWithStackTrace :: Str<strong>in</strong>g –> a


Demo


Programmatic access to <strong>stack</strong> <strong>trace</strong><br />

• The GHC.Stack module provides runtime<br />

access to the <strong>stack</strong> <strong>trace</strong><br />

• On top of which is built this:<br />

• e.g. now when GHC panics it emits a <strong>stack</strong><br />

<strong>trace</strong> (if it was compiled with profil<strong>in</strong>g)


Programmatic access to <strong>stack</strong> <strong>trace</strong><br />

• The GHC.Stack module provides runtime<br />

access to the <strong>stack</strong> <strong>trace</strong><br />

• On top of which is built this:<br />

-- | like '<strong>trace</strong>', but additionally pr<strong>in</strong>ts a call <strong>stack</strong> if one is<br />

-- available.<br />

<strong>trace</strong>Stack :: Str<strong>in</strong>g -> a -> a<br />

• e.g. now when GHC panics it emits a <strong>stack</strong><br />

<strong>trace</strong> (if it was compiled with profil<strong>in</strong>g)


Properties<br />

• This semantics has some nice properties.<br />

push L x<br />

=> x<br />

push L (λx . e) => λx . e<br />

push L (C x1 .. xn) => C x1 .. xn<br />

let x = λy . e <strong>in</strong> push L e'<br />

=> push L (let x = λy . e <strong>in</strong> e')<br />

push L (let x = e <strong>in</strong> e')<br />

=> let x = push L e <strong>in</strong> push L e


Properties<br />

• This semantics has some nice properties.<br />

push L x<br />

=> x<br />

push L (λx . e) => λx . e<br />

push L (C x1 .. xn) => C x1 .. xn<br />

s<strong>in</strong>ce the <strong>stack</strong><br />

attached to a<br />

lambda is<br />

irrelevant (except<br />

for heap profil<strong>in</strong>g)<br />

let x = λy . e <strong>in</strong> push L e'<br />

=> push L (let x = λy . e <strong>in</strong> e')<br />

push L (let x = e <strong>in</strong> e')<br />

=> let x = push L e <strong>in</strong> push L e


Properties<br />

• This semantics has some nice properties.<br />

push L x<br />

=> x<br />

push L (λx . e) => λx . e<br />

push L (C x1 .. xn) => C x1 .. xn<br />

O(1) change to cost<br />

attribution, no<br />

change to profile<br />

shape<br />

let x = λy . e <strong>in</strong> push L e'<br />

=> push L (let x = λy . e <strong>in</strong> e')<br />

push L (let x = e <strong>in</strong> e')<br />

=> let x = push L e <strong>in</strong> push L e


Properties<br />

• This semantics has some nice properties.<br />

push L x<br />

=> x<br />

push L (λx . e) => λx . e<br />

push L (C x1 .. xn) => C x1 .. xn<br />

let x = λy . e <strong>in</strong> push L e'<br />

=> push L (let x = λy . e <strong>in</strong> e')<br />

push L (let x = e <strong>in</strong> e')<br />

=> let x = push L e <strong>in</strong> push L e<br />

Note if e is a<br />

value, the push<br />

L will disappear


Inl<strong>in</strong><strong>in</strong>g<br />

• We expect to be able to substitute a function’s<br />

def<strong>in</strong>ition for its name without affect<strong>in</strong>g the<br />

<strong>stack</strong>. e.g.<br />

f = λx . push “f1” x+x<br />

ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong> f y<br />

• should be the same as<br />

ma<strong>in</strong> = λx. push “ma<strong>in</strong>”<br />

let y = 1 <strong>in</strong><br />

(λx . push “f1” x+x) y<br />

• and <strong>in</strong>deed it is <strong>in</strong> this semantics.<br />

– (<strong>in</strong>l<strong>in</strong><strong>in</strong>g functions is crucial for optimisation <strong>in</strong> GHC)


Th<strong>in</strong>k about what properties <strong>we</strong> want<br />

• Push <strong>in</strong>side lambda:<br />

push L (λ x. e) == λ x. push L e<br />

– (recall that the previous semantics allo<strong>we</strong>d<br />

dropp<strong>in</strong>g the push here)<br />

– This will give us a push that scopes over the <strong>in</strong>side<br />

of lambdas, not just outside.<br />

• which will <strong>in</strong> turn give us that <strong>stack</strong>s are robust to etaexpansion/contraction


What <strong>do</strong>es it take to make this true?<br />

• Consider<br />

let f = push “f” λ x . e<br />

<strong>in</strong> ... f ...<br />

let f = λ x . push “f” e<br />

<strong>in</strong> ... f ...<br />

• If <strong>we</strong> work through the details, <strong>we</strong> f<strong>in</strong>d that<br />

<strong>we</strong> need<br />

call S (push L S f ) == push L (call S S f )<br />

• Not difficult: e.g.<br />

type Stack = [Label]<br />

push = (:)<br />

call = foldr push<br />

like flip (++), but<br />

useful to def<strong>in</strong>e it<br />

this way


Recursion?<br />

• We <strong>do</strong> want f<strong>in</strong>ite <strong>stack</strong>s<br />

– the mutator is us<strong>in</strong>g tail recursion<br />

• Simplest approach: push is a no-op if the label<br />

is already on the <strong>stack</strong> somewhere:<br />

push l s | l `elem` s = s<br />

| otherwise = l : s<br />

• still satisfies the push-<strong>in</strong>side-lambda property<br />

• but: not so good for profil<strong>in</strong>g or debugg<strong>in</strong>g<br />

– the label on top of the <strong>stack</strong> is not necessarily<br />

where the program counter is


Inl<strong>in</strong><strong>in</strong>g of functions<br />

• (remember, allow<strong>in</strong>g <strong>in</strong>l<strong>in</strong><strong>in</strong>g is crucial)<br />

• Consider<br />

let g = λ x.e <strong>in</strong><br />

let f = push “f” g <strong>in</strong><br />

f y<br />

let f = push “f” λ x.e <strong>in</strong><br />

f y<br />

• Work through the details, and <strong>we</strong> need that<br />

call (push L S) S == push L S<br />

• <strong>in</strong>terest<strong>in</strong>g: call<strong>in</strong>g a function whose <strong>stack</strong> is a<br />

prefix of the current <strong>stack</strong> should not change<br />

the <strong>stack</strong>.


Break out the proof tools


Break out the proof tools<br />

• QuickCheck.


Break out the proof tools<br />

• QuickCheck.<br />

prop_append2 = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />

forAll Ma<strong>in</strong>.labels $ \x -><br />

call (s `push` x) s == s `push` x<br />

*** Failed! Falsifiable (after 8 tests and 2 shr<strong>in</strong>ks):<br />

(E :> "e") :> "b"<br />

"e"


Break out the proof tools<br />

• QuickCheck.<br />

prop_append2 = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />

forAll Ma<strong>in</strong>.labels $ \x -><br />

call (s `push` x) s == s `push` x<br />

*** Failed! Falsifiable (after 8 tests and 2 shr<strong>in</strong>ks):<br />

(E :> "e") :> "b"<br />

"e"<br />

• but this corresponds to<br />

someth<strong>in</strong>g very<br />

strange:


Break out the proof tools<br />

• QuickCheck.<br />

prop_append2 = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />

forAll Ma<strong>in</strong>.labels $ \x -><br />

call (s `push` x) s == s `push` x<br />

*** Failed! Falsifiable (after 8 tests and 2 shr<strong>in</strong>ks):<br />

(E :> "e") :> "b"<br />

"e"<br />

• but this corresponds to<br />

someth<strong>in</strong>g very<br />

strange:<br />

push “f”<br />

...<br />

let g = λ x.e <strong>in</strong><br />

let f = push “f” g <strong>in</strong><br />

f y


A more restricted property<br />

• This is a limited form of the real property <strong>we</strong><br />

need for <strong>in</strong>l<strong>in</strong><strong>in</strong>g<br />

• The push-<strong>in</strong>side-lambda property behaves<br />

similarly: <strong>we</strong> need to restrict the use of duplicate<br />

labels to make it go through.


A more restricted property<br />

prop_<strong>stack</strong>2a = forAllShr<strong>in</strong>k <strong>stack</strong>s shr<strong>in</strong>k<strong>stack</strong> $ \s -><br />

forAll Ma<strong>in</strong>.labels $ \x -><br />

x `elem<strong>stack</strong>` s ||<br />

call (s `push` x) s == s `push` x<br />

*Ma<strong>in</strong>> quickCheck prop_<strong>stack</strong>2a<br />

+++ OK, passed 100 tests.<br />

• This is a limited form of the real property <strong>we</strong><br />

need for <strong>in</strong>l<strong>in</strong><strong>in</strong>g<br />

• The push-<strong>in</strong>side-lambda property behaves<br />

similarly: <strong>we</strong> need to restrict the use of duplicate<br />

labels to make it go through.

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!