continuation passing style for effect handlers - formal ... ·...

Post on 08-Oct-2020

1 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

This work is supported by

Continuation Passing Style for Effect HandlersFormal Structures for Computation and Deduction

Daniel Hillerström 1 Sam Lindley 1

Robert Atkey 2 KC Sivaramakrishnan 3

1The University of Edinburgh, UK

2University of Strathclyde, UK

3University of Cambridge, UK

September 5, 2017

Algebraic effects by example: a drunk coin toss

Consider two effects: nondeterminism and exceptions

drunkToss : Toss ! {Choose : Bool;Fail : Zero}drunkToss = if do Choose then

if do Choose then Heads else Tailselse absurd do Fail

Drunk toss computation tree

Algebraic effects by example: a drunk coin toss

Consider two effects: nondeterminism and exceptions

drunkToss : Toss ! {Choose : Bool;Fail : Zero}drunkToss = if do Choose then

if do Choose then Heads else Tailselse absurd do Fail

Drunk toss computation treeChoose

Choose

Heads

true

Tails

false

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle drunkToss with fail) with allChoices⇒ [[Heads], [Tails], []]

handle (handle drunkToss with allChoices) with fail⇒ []

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with fail) with allChoicesChoose

Choose

Heads

true

Tails

false

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with fail) with allChoicesChoose

Choose

Heads

true

Tails

false

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with fail) with allChoicesChoose

Choose

[[Heads]]

true

Tails

false

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with fail) with allChoicesChoose

Choose

[[Heads]]

true

[[Tails]]

false

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with fail) with allChoicesChoose

[[Heads], [Tails]]

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with fail) with allChoicesChoose

[[Heads], [Tails]]

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with fail) with allChoicesChoose

[[Heads], [Tails]]

true

[[]]

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

[[Heads], [Tails], []]

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle drunkToss with allChoices) with fail⇒ []

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with allChoices) with failChoose

Choose

[Heads]

true

[Tails]

false

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with allChoices) with failChoose

[Heads,Tails]

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

handle (handle with allChoices) with failChoose

[Heads,Tails]

true

Fail

false

Effect handlers by example: modular interpretations

Choose handler

allChoices : α ! {Choose : Bool; ρ} ⇒ List α ! {ρ}allChoices = return x 7→ [x ]

Choose r 7→ r true ++ r false

Fail handler

fail : α ! {Fail : Zero; ρ} ⇒ List α ! {ρ}fail = return x 7→ [x ]

Fail r 7→ []

Two possible interpretations:

[]

Operational semantics for effect handlers

The operational semantics for handlers is fairly simple

handle (return V ) with H N[V /x ],where Hret = {return x 7→ N}

handle E [do ` V ] with H N[V /p, λy . handle E [return y ] with H/r ],where ` /∈ BL(E) and H` = {` p r 7→ N}

The set of bound labels, BL, is defined inductively

BL([ ]) = ∅ BL(let x ← E in N) = BL(E) BL(handle E with H) = BL(E) ∪ dom(H)

Evaluation contexts, E , are standard

E ::= [] | let x ← E in N | handle E with H

Why continuation passing style?

So, effect handlers are great! Now, how do we compile them?

Why continuation passing style (CPS)?CPS is good for implementing control flowCPS is formulated on a restricted subset of λ-calculusThe Links compiler (Cooper, Lindley, Wadler, and Yallop 2006)

Server-side uses a CEK machineClient-side uses CPS

Source and target calculi

Our source calculus is λρeff (Hillerström and Lindley 2016).

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | (`V )

Computations M,N ::= V W| let 〈` = x ; y〉 = V in N | case V {` x 7→ M; y 7→ N} | absurdV| return V | let x ← M in N| do ` V | handle M with H

Handlers H ::= {return x 7→ M} | {` p r 7→ M} ] H}

Our target is an untyped λ-calculus

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | ` VComputations M,N ::= V | M W | let 〈` = x ; y〉 = V in N

| case V {` x 7→ M; y 7→ N} | absurdV

Source and target calculi

Our source calculus is λρeff (Hillerström and Lindley 2016).

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | (`V )

Computations M,N ::= V W| let 〈` = x ; y〉 = V in N | case V {` x 7→ M; y 7→ N} | absurdV| return V | let x ← M in N| do ` V | handle M with H

Handlers H ::= {return x 7→ M} | {` p r 7→ M} ] H}

Our target is an untyped λ-calculus

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | ` VComputations M,N ::= V | M W | let 〈` = x ; y〉 = V in N

| case V {` x 7→ M; y 7→ N} | absurdV

Source and target calculi

Our source calculus is λρeff (Hillerström and Lindley 2016).

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | (`V )

Computations M,N ::= V W| let 〈` = x ; y〉 = V in N | case V {` x 7→ M; y 7→ N} | absurdV| return V | let x ← M in N| do ` V | handle M with H

Handlers H ::= {return x 7→ M} | {` p r 7→ M} ] H}

Our target is an untyped λ-calculus

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | ` VComputations M,N ::= V | M W | let 〈` = x ; y〉 = V in N

| case V {` x 7→ M; y 7→ N} | absurdV

Source and target calculi

Our source calculus is λρeff (Hillerström and Lindley 2016).

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | (`V )

Computations M,N ::= V W| let 〈` = x ; y〉 = V in N | case V {` x 7→ M; y 7→ N} | absurdV| return V | let x ← M in N| do ` V | handle M with H

Handlers H ::= {return x 7→ M} | {` p r 7→ M} ] H}

Our target is an untyped λ-calculus

Values V ,W ::= x | λx .M | 〈〉 | 〈` = V ;W 〉 | ` VComputations M,N ::= V | M W | let 〈` = x ; y〉 = V in N

| case V {` x 7→ M; y 7→ N} | absurdV

Curried CPS translation for fine-grain call-by-value

The continuation represents the execution context.

Computations

JV W K = JV K JW KJlet 〈` = x ; y〉 = V in NK = let 〈` = x ; y〉 = JV K in JNK

Jcase V {` x 7→ M; y 7→ N}K = case JV K {` x 7→ JMK; y 7→ JNK}Jabsurd V K = absurd JV KJreturn V K = λk.k JV K

Jlet x ← M in NK = λk.JMK(λx .JNKk)

ValuesJxK = x

Jλx .MK = λx .JMKJ〈〉K = 〈〉

J〈` = V ;W 〉K = 〈` = JV K; JW K〉J` V K = ` JV K

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z)

((λx .λh.x) 〈〉) (λz .absurd z) (λh.〈〉) (λz .absurd z) 〈〉

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z)

((λx .λh.x) 〈〉) (λz .absurd z) (λh.〈〉) (λz .absurd z) 〈〉

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z)

((λx .λh.x) 〈〉) (λz .absurd z) (λh.〈〉) (λz .absurd z) 〈〉

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z)

((λx .λh.x) 〈〉) (λz .absurd z) (λh.〈〉) (λz .absurd z) 〈〉

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z)

((λx .λh.x) 〈〉) (λz .absurd z) (λh.〈〉) (λz .absurd z) 〈〉

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z) ((λx .λh.x) 〈〉) (λz .absurd z)

(λh.〈〉) (λz .absurd z) 〈〉

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z) ((λx .λh.x) 〈〉) (λz .absurd z) (λh.〈〉) (λz .absurd z)

〈〉

Curried CPS for handlers

With handlers there are two contexts: a pure and an effect context.Idea: implicit stack of alternating pure and effect continuations.

Jdo ` V K = λk.λh.h (` 〈JV K, λx .k x h〉)Jhandle M with HK = JMK JHretK JHopsKJ{return x 7→ N}K = λx .λh.JNK

J{` p r 7→ N`}`∈LK = λz .case z {(` 〈p, r〉 7→ JN`K)`∈L; y 7→ Mforward}Mforward = λk ′.λh′.vmap (λ〈p, r〉 k.k 〈p, λx .r x k ′ h′〉) y h′

>JMK = JMK (λx .λh.x) (λz .absurd z)

Problem: J−K yields administrative redexes and is not properly tail-recursive!

>Jreturn 〈〉K = (λk.k 〈〉) (λx .λh.x) (λz .absurd z) ((λx .λh.x) 〈〉) (λz .absurd z) (λh.〈〉) (λz .absurd z) 〈〉

Uncurried CPS for handlers

Idea: make the continuation stack explicit (Materzok and Biernacki 2012).

Jreturn V K = λ(k :: ks).k JV K ksJlet x ← M in NK = λ(k :: ks).JMK((λx ks.JNK(k :: ks)) :: ks)

Jdo ` V K = λ(k :: h :: ks).h (` 〈JV K, h :: k :: []〉) ksJhandle M with HK = λks.JMK(JHretK :: JHopsK :: ks)J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK ks

J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K ks)`∈L;y 7→ Mforward}

Mforward = let (k ′ :: h′ :: ks ′) = ks invmap (λ〈p, r〉 (k :: ks).k 〈p, h′ :: k ′ :: r〉 ks) y ks ′

>JMK = JMK ((λx ks.x) :: (λz ks.absurd z) :: [])

But J−K still yields the administrative redexes

>Jreturn 〈〉K = (λ(k :: ks).k 〈〉 ks) ((λx ks.x) :: (λz .absurd z) :: [])

(λx ks.x) 〈〉 [(λz .absurd z)] + 〈〉

Uncurried CPS for handlers

Idea: make the continuation stack explicit (Materzok and Biernacki 2012).

Jreturn V K = λ(k :: ks).k JV K ksJlet x ← M in NK = λ(k :: ks).JMK((λx ks.JNK(k :: ks)) :: ks)

Jdo ` V K = λ(k :: h :: ks).h (` 〈JV K, h :: k :: []〉) ksJhandle M with HK = λks.JMK(JHretK :: JHopsK :: ks)J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK ks

J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K ks)`∈L;y 7→ Mforward}

Mforward = let (k ′ :: h′ :: ks ′) = ks invmap (λ〈p, r〉 (k :: ks).k 〈p, h′ :: k ′ :: r〉 ks) y ks ′

>JMK = JMK ((λx ks.x) :: (λz ks.absurd z) :: [])

But J−K still yields the administrative redexes

>Jreturn 〈〉K = (λ(k :: ks).k 〈〉 ks) ((λx ks.x) :: (λz .absurd z) :: [])

(λx ks.x) 〈〉 [(λz .absurd z)] + 〈〉

Uncurried CPS for handlers

Idea: make the continuation stack explicit (Materzok and Biernacki 2012).

Jreturn V K = λ(k :: ks).k JV K ksJlet x ← M in NK = λ(k :: ks).JMK((λx ks.JNK(k :: ks)) :: ks)

Jdo ` V K = λ(k :: h :: ks).h (` 〈JV K, h :: k :: []〉) ksJhandle M with HK = λks.JMK(JHretK :: JHopsK :: ks)J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK ks

J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K ks)`∈L;y 7→ Mforward}

Mforward = let (k ′ :: h′ :: ks ′) = ks invmap (λ〈p, r〉 (k :: ks).k 〈p, h′ :: k ′ :: r〉 ks) y ks ′

>JMK = JMK ((λx ks.x) :: (λz ks.absurd z) :: [])

But J−K still yields the administrative redexes

>Jreturn 〈〉K = (λ(k :: ks).k 〈〉 ks) ((λx ks.x) :: (λz .absurd z) :: [])

(λx ks.x) 〈〉 [(λz .absurd z)] + 〈〉

Uncurried CPS for handlers

Idea: make the continuation stack explicit (Materzok and Biernacki 2012).

Jreturn V K = λ(k :: ks).k JV K ksJlet x ← M in NK = λ(k :: ks).JMK((λx ks.JNK(k :: ks)) :: ks)

Jdo ` V K = λ(k :: h :: ks).h (` 〈JV K, h :: k :: []〉) ksJhandle M with HK = λks.JMK(JHretK :: JHopsK :: ks)J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK ks

J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K ks)`∈L;y 7→ Mforward}

Mforward = let (k ′ :: h′ :: ks ′) = ks invmap (λ〈p, r〉 (k :: ks).k 〈p, h′ :: k ′ :: r〉 ks) y ks ′

>JMK = JMK ((λx ks.x) :: (λz ks.absurd z) :: [])

But J−K still yields the administrative redexes

>Jreturn 〈〉K = (λ(k :: ks).k 〈〉 ks) ((λx ks.x) :: (λz .absurd z) :: [])

(λx ks.x) 〈〉 [(λz .absurd z)] + 〈〉

Uncurried CPS for handlers

Idea: make the continuation stack explicit (Materzok and Biernacki 2012).

Jreturn V K = λ(k :: ks).k JV K ksJlet x ← M in NK = λ(k :: ks).JMK((λx ks.JNK(k :: ks)) :: ks)

Jdo ` V K = λ(k :: h :: ks).h (` 〈JV K, h :: k :: []〉) ksJhandle M with HK = λks.JMK(JHretK :: JHopsK :: ks)J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK ks

J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K ks)`∈L;y 7→ Mforward}

Mforward = let (k ′ :: h′ :: ks ′) = ks invmap (λ〈p, r〉 (k :: ks).k 〈p, h′ :: k ′ :: r〉 ks) y ks ′

>JMK = JMK ((λx ks.x) :: (λz ks.absurd z) :: [])

But J−K still yields the administrative redexes

>Jreturn 〈〉K = (λ(k :: ks).k 〈〉 ks) ((λx ks.x) :: (λz .absurd z) :: [])

(λx ks.x) 〈〉 [(λz .absurd z)] + 〈〉

Uncurried CPS for handlers

Idea: make the continuation stack explicit (Materzok and Biernacki 2012).

Jreturn V K = λ(k :: ks).k JV K ksJlet x ← M in NK = λ(k :: ks).JMK((λx ks.JNK(k :: ks)) :: ks)

Jdo ` V K = λ(k :: h :: ks).h (` 〈JV K, h :: k :: []〉) ksJhandle M with HK = λks.JMK(JHretK :: JHopsK :: ks)J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK ks

J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K ks)`∈L;y 7→ Mforward}

Mforward = let (k ′ :: h′ :: ks ′) = ks invmap (λ〈p, r〉 (k :: ks).k 〈p, h′ :: k ′ :: r〉 ks) y ks ′

>JMK = JMK ((λx ks.x) :: (λz ks.absurd z) :: [])

But J−K still yields the administrative redexes

>Jreturn 〈〉K = (λ(k :: ks).k 〈〉 ks) ((λx ks.x) :: (λz .absurd z) :: []) (λx ks.x) 〈〉 [(λz .absurd z)]

+ 〈〉

Uncurried CPS for handlers

Idea: make the continuation stack explicit (Materzok and Biernacki 2012).

Jreturn V K = λ(k :: ks).k JV K ksJlet x ← M in NK = λ(k :: ks).JMK((λx ks.JNK(k :: ks)) :: ks)

Jdo ` V K = λ(k :: h :: ks).h (` 〈JV K, h :: k :: []〉) ksJhandle M with HK = λks.JMK(JHretK :: JHopsK :: ks)J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK ks

J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K ks)`∈L;y 7→ Mforward}

Mforward = let (k ′ :: h′ :: ks ′) = ks invmap (λ〈p, r〉 (k :: ks).k 〈p, h′ :: k ′ :: r〉 ks) y ks ′

>JMK = JMK ((λx ks.x) :: (λz ks.absurd z) :: [])

But J−K still yields the administrative redexes

>Jreturn 〈〉K = (λ(k :: ks).k 〈〉 ks) ((λx ks.x) :: (λz .absurd z) :: []) (λx ks.x) 〈〉 [(λz .absurd z)] + 〈〉

Higher-order CPS for handlers (I)

Idea: adopt a two level λ-calculus (Danvy and Filinski 1990; Danvy and Nielsen2003) with two kinds of continuations

Static continuations κDynamic continuations k

Correspondingly, two kinds of reductionsStatic reductions at translation timeDynamic reductions at run-time

Move between levels using reflection (↑) and reification (↓)

↓↑V = V↓(V :: VS) = V :: ↓VS

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: []) + 〈〉

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: []) + 〈〉

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: []) + 〈〉

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: []) + 〈〉

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: []) + 〈〉

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])

= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: []) + 〈〉

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: [])

+ 〈〉

Higher-order CPS for handlers (II)

Two kinds of reductions: static and dynamic .

Jreturn V K = λκ :: κs.κ@ JV K @ ↓κsJlet x ← M in NK = λκ :: κs.JMK @ ((λx ks.JNK @ (κ :: ↑ks)) :: κs)

Jdo ` V K = λκ :: η :: κs.η @ (` 〈JV K, η :: κ :: []〉) @ ↓κsJhandle M with HK = λκs.JMK @ (JHretK :: JHopsK :: κs)

J{return x 7→ N}K = λx ks.let (h :: ks ′) = ks in JNK @ ↑ks ′J{(` p r 7→ N`)`∈L}K = λz ks.case z {(` 〈p, s〉 7→ let r = fun s in JN`K @ ↑ks)`∈L;

y 7→ Mforward}Mforward = let (k ′ :: h′ :: ks ′) = ks in

vmap (λ〈p, s〉 (k :: ks).k 〈p, h′ :: k ′ :: s〉 ks) y ks ′

>JMK = JMK @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])

J−K produces no statically eliminatable administrative redexes

>Jreturn 〈〉K = (λκ :: κs.κ@ 〈〉@ ↓κ) @ ((λx ks.x) :: (λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ↓((λz ks.absurd z) :: ↑[])= (λx ks.x) @ 〈〉@ ((λz ks.absurd z) :: []) + 〈〉

Correctness

Definition (Translation of evaluation contexts)

J[ ]K = λκs. κsJlet x ← E in NK = λκ :: κs. JEK @ ((λx ks.JNK @ (k :: ↑ks)) :: κs)

Jhandle E with HK = λκs. JEK @ (JHretK :: JHopsK :: κs)

Lemma (Decomposition)The characteristic property of the CPS translation on evaluation contexts

JE [M]K @ (V :: VS) = JMK @ (JEK @ (V :: VS))

Theorem (Simulation)If M N then >JMK + ∗

a >JNK.

Conclusions

In summaryhandlers provide a modular abstraction for user-defined computational effects,first full CPS translations for handlers,the first-order curried CPS is neat, but inpractical,and the first-order uncurried CPS is naïve,refectified by a higher-order uncurried CPS.

Full details in the paper along witha discussion on the implementation of the higher-order CPS,a sketch of a typed higher-order CPS translation,an adaptation to accommodate shallow handlers,and a discussion on adapting the translation to a language like OCaml.

An implementation is available in Links

https://github.com/links-lang/links

References

Danvy, Olivier and Andrzej Filinski (1990). “Abstracting Control”. In: LISP andFunctional Programming, pp. 151–160.

Danvy, Olivier and Lasse R. Nielsen (2003). “A first-order one-pass CPStransformation”. In: Theor. Comput. Sci. 308.1-3, pp. 239–257.

Cooper, Ezra et al. (2006). “Links: Web Programming Without Tiers”. In:FMCO. Vol. 4709. LNCS. Springer, pp. 266–296.

Materzok, Marek and Dariusz Biernacki (2012). “A Dynamic Interpretation of theCPS Hierarchy”. In: APLAS. Vol. 7705. LNCS. Springer, pp. 296–311.

Hillerström, Daniel and Sam Lindley (2016). “Liberating effects with rows andhandlers”. In: TyDe@ICFP. ACM, pp. 15–27.

top related