SLIDE 1
Retrofitting Purity with Comonads
Neel Krishnaswami June 25, 2018
University of Cambridge
SLIDE 2 Once Upon a Time
- There was a PhD student
- who finished her dissertation…
1
SLIDE 3 Once Upon a Time
- There was a PhD student
- who finished her dissertation…
1
SLIDE 4 Once Upon a Time
- There was a PhD student
- who finished her dissertation…
1
SLIDE 5 Once Upon a Time
- Her advisor said, “It’s time for you to go out into the wide
world!”
- So she did, and she designed a programming language
2
SLIDE 6 Once Upon a Time
- Her advisor said, “It’s time for you to go out into the wide
world!”
- So she did, and she designed a programming language
2
SLIDE 7 Once Upon a Time
- Her advisor said, “It’s time for you to go out into the wide
world!”
- So she did, and she designed a programming language
2
SLIDE 8
A Functional Language
data List a = [] | a :: (List a) len : List a -> Integer len [] = 0 len (x :: xs) = 1 + len xs map : (a -> b) -> List a -> List b map f [] = [] map f (x :: xs) = f x :: map f xs
3
SLIDE 9
A Functional Language
data List a = [] | a :: (List a) len : List a -> Integer len [] = 0 len (x :: xs) = 1 + len xs map : (a -> b) -> List a -> List b map f [] = [] map f (x :: xs) = f x :: map f xs
3
SLIDE 10
A Functional Language
data List a = [] | a :: (List a) len : List a -> Integer len [] = 0 len (x :: xs) = 1 + len xs map : (a -> b) -> List a -> List b map f [] = [] map f (x :: xs) = f x :: map f xs
3
SLIDE 11 Once Upon a Time
- While implementing it, she added one primitive:
print : String -> Unit print = Runtime.Primitive.Magic.__printf
- Nothing bad happened…yet!
4
SLIDE 12 Once Upon a Time
- While implementing it, she added one primitive:
print : String -> Unit print = Runtime.Primitive.Magic.__printf
- Nothing bad happened…yet!
4
SLIDE 13 Once Upon a Time
- While implementing it, she added one primitive:
print : String -> Unit print = Runtime.Primitive.Magic.__printf
- Nothing bad happened…yet!
4
SLIDE 14 Once Upon a Time
- While implementing it, she added one primitive:
print : String -> Unit print = Runtime.Primitive.Magic.__printf
yet!
4
SLIDE 15 Once Upon a Time
- While implementing it, she added one primitive:
print : String -> Unit print = Runtime.Primitive.Magic.__printf
- Nothing bad happened…yet!
4
SLIDE 16 Once Upon a Time
- Naturally, this language was wildly successful
- Our protagonist achieved fame and fortune
- …and feature requests and bug reports
5
SLIDE 17 Once Upon a Time
- Naturally, this language was wildly successful
- Our protagonist achieved fame and fortune
- …and feature requests and bug reports
5
SLIDE 18 Once Upon a Time
- Naturally, this language was wildly successful
- Our protagonist achieved fame and fortune
- …and feature requests and bug reports
5
SLIDE 19 Once Upon a Time
- Naturally, this language was wildly successful
- Our protagonist achieved fame and fortune
- …and feature requests and bug reports
5
SLIDE 20 Feature Request: List Fusion
- A user wrote the following code:
map f (map g reallyBigList)
- and complained that it allocated a really big intermediate
list
6
SLIDE 21 Feature Request: List Fusion
- Our protagonist wrote a compiler pass to turn this:
map f (map g reallyBigList)
map (f o g) reallyBigList
- Much RAM was saved!
- Benchmarks improved!
7
SLIDE 22 Feature Request: List Fusion
- Our protagonist wrote a compiler pass to turn this:
map f (map g reallyBigList)
map (f o g) reallyBigList
- Much RAM was saved!
- Benchmarks improved!
7
SLIDE 23 Feature Request: List Fusion
- Our protagonist wrote a compiler pass to turn this:
map f (map g reallyBigList)
map (f o g) reallyBigList
- Much RAM was saved!
- Benchmarks improved!
7
SLIDE 24 Feature Request: List Fusion
- Our protagonist wrote a compiler pass to turn this:
map f (map g reallyBigList)
map (f o g) reallyBigList
- Much RAM was saved!
- Benchmarks improved!
7
SLIDE 25 Feature Request: List Fusion
- Our protagonist wrote a compiler pass to turn this:
map f (map g reallyBigList)
map (f o g) reallyBigList
- Much RAM was saved!
- Benchmarks improved!
7
SLIDE 26 Bug Reports
f : Int -> Int f n = print "a"; n + 1 g : Int -> Int g n = print "b"; n + 1 printList (map f (map g [1, 2, 3]))
- In the old version, it printed:
bbbaaa[3, 4, 5]
- In the “optimized” version, it printed:
bababa[3, 4, 5]
8
SLIDE 27 Bug Reports
f : Int -> Int f n = print "a"; n + 1 g : Int -> Int g n = print "b"; n + 1 printList (map f (map g [1, 2, 3]))
- In the old version, it printed:
bbbaaa[3, 4, 5]
- In the “optimized” version, it printed:
bababa[3, 4, 5]
8
SLIDE 28 Bug Reports
f : Int -> Int f n = print "a"; n + 1 g : Int -> Int g n = print "b"; n + 1 printList (map f (map g [1, 2, 3]))
- In the old version, it printed:
bbbaaa[3, 4, 5]
- In the “optimized” version, it printed:
bababa[3, 4, 5]
8
SLIDE 29 Bug Reports
f : Int -> Int f n = print "a"; n + 1 g : Int -> Int g n = print "b"; n + 1 printList (map f (map g [1, 2, 3]))
- In the old version, it printed:
bbbaaa[3, 4, 5]
- In the “optimized” version, it printed:
bababa[3, 4, 5]
8
SLIDE 30 Narrative Tension!
- Our protagonist was worried:
- She wanted purity for optimization purposes
- But her language was already impure
- Was she out of luck?
9
SLIDE 31 Narrative Tension!
- Our protagonist was worried:
- She wanted purity for optimization purposes
- But her language was already impure
- Was she out of luck?
9
SLIDE 32 Narrative Tension!
- Our protagonist was worried:
- She wanted purity for optimization purposes
- But her language was already impure
- Was she out of luck?
9
SLIDE 33 Narrative Tension!
- Our protagonist was worried:
- She wanted purity for optimization purposes
- But her language was already impure
- Was she out of luck?
9
SLIDE 34 Narrative Tension!
- Our protagonist was worried:
- She wanted purity for optimization purposes
- But her language was already impure
- Was she out of luck?
9
SLIDE 35
Syntax
Types A ::= File | char | A → B Pure A Terms e ::= x | c | e.print(e′) | λx.e | e e′ pure e let pure x e in e Contexts Γ ::= · | Γ, x : A x A Judgements Γ ⊢ e : A
10
SLIDE 36
Syntax
Types A ::= File | char | A → B | Pure A Terms e ::= x | c | e.print(e′) | λx.e | e e′ | pure(e) | let pure(x) = e in e′ Contexts Γ ::= · | Γ, x : A | Γ, x :: A Judgements Γ ⊢ e : A
10
SLIDE 37
Typing Rules
x : A ∈ Γ x A Γ ⊢ x : A Γ ⊢ e : File Γ ⊢ e′ : char Γ ⊢ e.print(e′) : 1 Γ, x : A ⊢ e : B Γ ⊢ λx.e : A → B Γ ⊢ e : A → B Γ ⊢ e′ : A Γ ⊢ e e′ : B
pure
e A pure e Pure A e Pure A x A e C let pure x e in e C
pure
x A pure
pure
x A pure
pure x
A
11
SLIDE 38
Typing Rules
x : A ∈ Γ ∨ x :: A ∈ Γ Γ ⊢ x : A Γ ⊢ e : File Γ ⊢ e′ : char Γ ⊢ e.print(e′) : 1 Γ, x : A ⊢ e : B Γ ⊢ λx.e : A → B Γ ⊢ e : A → B Γ ⊢ e′ : A Γ ⊢ e e′ : B
pure
e A pure e Pure A e Pure A x A e C let pure x e in e C
pure
x A pure
pure
x A pure
pure x
A
11
SLIDE 39
Typing Rules
x : A ∈ Γ ∨ x :: A ∈ Γ Γ ⊢ x : A Γ ⊢ e : File Γ ⊢ e′ : char Γ ⊢ e.print(e′) : 1 Γ, x : A ⊢ e : B Γ ⊢ λx.e : A → B Γ ⊢ e : A → B Γ ⊢ e′ : A Γ ⊢ e e′ : B Γpure ⊢ e : A Γ ⊢ pure(e) : Pure(A) e Pure A x A e C let pure x e in e C (·)pure = · (Γ, x : A)pure = Γpure (Γ, x :: A)pure = Γpure, x :: A
11
SLIDE 40
Typing Rules
x : A ∈ Γ ∨ x :: A ∈ Γ Γ ⊢ x : A Γ ⊢ e : File Γ ⊢ e′ : char Γ ⊢ e.print(e′) : 1 Γ, x : A ⊢ e : B Γ ⊢ λx.e : A → B Γ ⊢ e : A → B Γ ⊢ e′ : A Γ ⊢ e e′ : B Γpure ⊢ e : A Γ ⊢ pure(e) : Pure(A) Γ ⊢ e : Pure(A) Γ, x :: A ⊢ e′ : C Γ ⊢ let pure(x) = e in e′ : C (·)pure = · (Γ, x : A)pure = Γpure (Γ, x :: A)pure = Γpure, x :: A
11
SLIDE 41
A Pure Map Function
data List a = [] | a :: (List a) map : Pure(a -> b) -> List a -> List b map (pure f) [] = [] map (pure f) (x :: xs) = f x :: map (pure f) xs
12
SLIDE 42
A Pure Map Function
data List a = [] | a :: (List a) map : Pure(a -> b) -> List a -> List b map (pure f) [] = [] map (pure f) (x :: xs) = f x :: map (pure f) xs
12
SLIDE 43 Principles of Retrofitted Purity
- We have ordinary and pure variables
- We add a type for “pure values”
- Pure values can only refer to pure variables
- Imperative functions like print are bound to ordinary
variables
13
SLIDE 44 Principles of Retrofitted Purity
- We have ordinary and pure variables
- We add a type for “pure values”
- Pure values can only refer to pure variables
- Imperative functions like print are bound to ordinary
variables
13
SLIDE 45 Principles of Retrofitted Purity
- We have ordinary and pure variables
- We add a type for “pure values”
- Pure values can only refer to pure variables
- Imperative functions like print are bound to ordinary
variables
13
SLIDE 46 Principles of Retrofitted Purity
- We have ordinary and pure variables
- We add a type for “pure values”
- Pure values can only refer to pure variables
- Imperative functions like print are bound to ordinary
variables
13
SLIDE 47 Principles of Retrofitted Purity
- We have ordinary and pure variables
- We add a type for “pure values”
- Pure values can only refer to pure variables
- Imperative functions like print are bound to ordinary
variables
13
SLIDE 48 Principles of Retrofitted Purity
- We have ordinary and pure variables
- We add a type for “pure values”
- Pure values can only refer to pure variables
- Imperative functions like print are bound to ordinary
variables
13
SLIDE 49 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space X w is a set X and a weight function
w X C
- Elements of X are values
- Given a value x, the weight w x is the set of capabilities it
- wns
- Given capability spaces X wX and Y wY , a function
f X Y is capability-respecting when wY f x wX x
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 50 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space X w is a set X and a weight function
w X C
- Elements of X are values
- Given a value x, the weight w x is the set of capabilities it
- wns
- Given capability spaces X wX and Y wY , a function
f X Y is capability-respecting when wY f x wX x
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 51 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space X w is a set X and a weight function
w X C
- Elements of X are values
- Given a value x, the weight w x is the set of capabilities it
- wns
- Given capability spaces X wX and Y wY , a function
f X Y is capability-respecting when wY f x wX x
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 52 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space (X, w) is a set X and a weight function
w : X → P(C)
- Elements of X are values
- Given a value x, the weight w x is the set of capabilities it
- wns
- Given capability spaces X wX and Y wY , a function
f X Y is capability-respecting when wY f x wX x
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 53 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space (X, w) is a set X and a weight function
w : X → P(C)
- Elements of X are values
- Given a value x, the weight w x is the set of capabilities it
- wns
- Given capability spaces X wX and Y wY , a function
f X Y is capability-respecting when wY f x wX x
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 54 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space (X, w) is a set X and a weight function
w : X → P(C)
- Elements of X are values
- Given a value x, the weight w(x) is the set of capabilities it
- wns
- Given capability spaces X wX and Y wY , a function
f X Y is capability-respecting when wY f x wX x
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 55 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space (X, w) is a set X and a weight function
w : X → P(C)
- Elements of X are values
- Given a value x, the weight w(x) is the set of capabilities it
- wns
- Given capability spaces (X, wX) and (Y, wY), a function
f : X → Y is capability-respecting when wY(f(x)) ⊆ wX(x)
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 56 Semantics
- Let C be a set of capabilities
- In our example, C is the set of file handles
- A capability space (X, w) is a set X and a weight function
w : X → P(C)
- Elements of X are values
- Given a value x, the weight w(x) is the set of capabilities it
- wns
- Given capability spaces (X, wX) and (Y, wY), a function
f : X → Y is capability-respecting when wY(f(x)) ⊆ wX(x)
- Cap is the the category of capability spaces and
capability-respecting functions.
14
SLIDE 57 Products in Cap
Given capability spaces (X, wX) and (Y, wY):
- Define (X, wX) × (Y, wY) = (X × Y, wX×Y) where
wX×Y(x, y) = wX(x) ∪ wY(y)
fst X Y X fst x y x snd X Y Y snd x y y
15
SLIDE 58 Products in Cap
Given capability spaces (X, wX) and (Y, wY):
- Define (X, wX) × (Y, wY) = (X × Y, wX×Y) where
wX×Y(x, y) = wX(x) ∪ wY(y)
fst : X × Y → X fst(x, y) = x snd : X × Y → Y snd(x, y) = y
15
SLIDE 59 Cartesian Closure of Cap
Given capability spaces (X, wX) and (Y, wY):
Y wY Z wX
Y where
Z f X Y c C x X wY f x wX x c wX
Y f
c C x X wY f x wX x c
- Intuition: weight of a function value comes from the
weight of the captured variables of its closure
16
SLIDE 60 Cartesian Closure of Cap
Given capability spaces (X, wX) and (Y, wY):
- (X, wX) → (Y, wY) = (Z, wX→Y) where
Z f X Y c C x X wY f x wX x c wX
Y f
c C x X wY f x wX x c
- Intuition: weight of a function value comes from the
weight of the captured variables of its closure
16
SLIDE 61 Cartesian Closure of Cap
Given capability spaces (X, wX) and (Y, wY):
- (X, wX) → (Y, wY) = (Z, wX→Y) where
Z = {f ∈ X → Y | ∃c ⊆ C. ∀x ∈ X. wY(f(x)) ⊆ wX(x) ∪ c} wX
Y f
c C x X wY f x wX x c
- Intuition: weight of a function value comes from the
weight of the captured variables of its closure
16
SLIDE 62 Cartesian Closure of Cap
Given capability spaces (X, wX) and (Y, wY):
- (X, wX) → (Y, wY) = (Z, wX→Y) where
Z = {f ∈ X → Y | ∃c ⊆ C. ∀x ∈ X. wY(f(x)) ⊆ wX(x) ∪ c} wX→Y(f) = min {c ∈ P(C) | ∀x ∈ X. wY(f(x)) ⊆ wX(x) ∪ c}
- Intuition: weight of a function value comes from the
weight of the captured variables of its closure
16
SLIDE 63 Cartesian Closure of Cap
Given capability spaces (X, wX) and (Y, wY):
- (X, wX) → (Y, wY) = (Z, wX→Y) where
Z = {f ∈ X → Y | ∃c ⊆ C. ∀x ∈ X. wY(f(x)) ⊆ wX(x) ∪ c} wX→Y(f) = min {c ∈ P(C) | ∀x ∈ X. wY(f(x)) ⊆ wX(x) ∪ c}
- Intuition: weight of a function value comes from the
weight of the captured variables of its closure
16
SLIDE 64 A Writer Monad
We can define a monad on Cap as follows.
Z ≜ X × (C → String) wZ(x, o) = wX(x) ∪ {c ∈ C | o(c) ̸= ””}
- We can define the unit ηX : X → T(X) as
ηX(x) = (x, λc.””)
- We can define the multiplication µX : T(T(X)) → T(X) as
µX((x, o), o′) = (x, λc.o′(c) · o(c))
17
SLIDE 65 A Purity Comonad
Z = {x ∈ X | wX(x) = ∅} wZ(x) = wX(x) = ∅
- We can define ϵX : □(X) → X as
ϵX(x) = x
- We can define δX : □(X) → □(□X) as
δX(x) = x
18
SLIDE 66 Escaping the Monad!
There is a capability-respecting function πX : □(T X) → □X: πX(x, o) = x This looks trivial, but recall that wT X x o wX x c C
The comonadic denial of capability ownership lets us escape!
19
SLIDE 67
Escaping the Monad!
There is a capability-respecting function πX : □(T X) → □X: πX(x, o) = x This looks trivial, but recall that wT(X)(x, o) = wX(x) ∪ {c ∈ C | o(c) ̸= ””} The comonadic denial of capability ownership lets us escape!
19
SLIDE 68
Escaping the Monad!
There is a capability-respecting function πX : □(T X) → □X: πX(x, o) = x This looks trivial, but recall that wT(X)(x, o) = wX(x) ∪ {c ∈ C | o(c) ̸= ””} The comonadic denial of capability ownership lets us escape!
19
SLIDE 69
Interpreting Types
We can interpret our programming language using the standard call-by-value interpretation of effectful functions: File = C char = {0 . . . 255} A → B = A → TB Pure(A) = □A 1 x A A x A A e A T A
20
SLIDE 70
Interpreting Types
We can interpret our programming language using the standard call-by-value interpretation of effectful functions: File = C char = {0 . . . 255} A → B = A → TB Pure(A) = □A · = 1 Γ, x : A = Γ × A Γ, x :: A = Γ × □A e A T A
20
SLIDE 71
Interpreting Types
We can interpret our programming language using the standard call-by-value interpretation of effectful functions: File = C char = {0 . . . 255} A → B = A → TB Pure(A) = □A · = 1 Γ, x : A = Γ × A Γ, x :: A = Γ × □A Γ ⊢ e : A ∈ Γ → TA
20
SLIDE 72 Semantics of Terms
Γ ⊢ e : A ∈ Γ → TA x return x x e return v e v x e1 e2 do f e1 v e2 f v pure e return e
Pure
let pure x e in e do v e e v x e1 print e2 let f o1 e1 in let c o2 e2 in let o3 n o2 n
c
21
SLIDE 73 Semantics of Terms
Γ ⊢ e : A ∈ Γ → TA x γ = return γ(x) x e return v e v x e1 e2 do f e1 v e2 f v pure e return e
Pure
let pure x e in e do v e e v x e1 print e2 let f o1 e1 in let c o2 e2 in let o3 n o2 n
c
21
SLIDE 74 Semantics of Terms
Γ ⊢ e : A ∈ Γ → TA x γ = return γ(x) λx.e γ = return (λv.e(γ, v/x)) e1 e2 do f e1 v e2 f v pure e return e
Pure
let pure x e in e do v e e v x e1 print e2 let f o1 e1 in let c o2 e2 in let o3 n o2 n
c
21
SLIDE 75 Semantics of Terms
Γ ⊢ e : A ∈ Γ → TA x γ = return γ(x) λx.e γ = return (λv.e(γ, v/x)) e1 e2 γ = do f ← e1 γ v ← e2 γ f(v) pure e return e
Pure
let pure x e in e do v e e v x e1 print e2 let f o1 e1 in let c o2 e2 in let o3 n o2 n
c
21
SLIDE 76 Semantics of Terms
Γ ⊢ e : A ∈ Γ → TA x γ = return γ(x) λx.e γ = return (λv.e(γ, v/x)) e1 e2 γ = do f ← e1 γ v ← e2 γ f(v) pure(e) γ = return (π(e γPure)) let pure x e in e do v e e v x e1 print e2 let f o1 e1 in let c o2 e2 in let o3 n o2 n
c
21
SLIDE 77 Semantics of Terms
Γ ⊢ e : A ∈ Γ → TA x γ = return γ(x) λx.e γ = return (λv.e(γ, v/x)) e1 e2 γ = do f ← e1 γ v ← e2 γ f(v) pure(e) γ = return (π(e γPure)) let pure(x) = e in e′ γ = do v ← e γ e′ (γ, v/x) e1 print e2 let f o1 e1 in let c o2 e2 in let o3 n o2 n
c
21
SLIDE 78
Semantics of Terms
Γ ⊢ e : A ∈ Γ → TA x γ = return γ(x) λx.e γ = return (λv.e(γ, v/x)) e1 e2 γ = do f ← e1 γ v ← e2 γ f(v) pure(e) γ = return (π(e γPure)) let pure(x) = e in e′ γ = do v ← e γ e′ (γ, v/x) e1.print(e2) γ = let (f, o1) = e1 γ in let (c, o2) = e2 γ in let o3 = λn.o2(n) · o1(n) in (∗, [o3|f : o3(f) · c])
21
SLIDE 79 Conclusion
Our heroine added comonadic purity to her programming language:
- She had a sound semantics and a clean type theory
- Fusion worked for pure functions
- Backwards compatibility was retained for effectful code
- Her systems programmer friends were happy she had a
capability-safe language
- And she grew up to be a dinosaur pirate witch PL designer.
22
SLIDE 80 Conclusion
Our heroine added comonadic purity to her programming language:
- She had a sound semantics and a clean type theory
- Fusion worked for pure functions
- Backwards compatibility was retained for effectful code
- Her systems programmer friends were happy she had a
capability-safe language
- And she grew up to be a dinosaur pirate witch PL designer.
22
SLIDE 81 Conclusion
Our heroine added comonadic purity to her programming language:
- She had a sound semantics and a clean type theory
- Fusion worked for pure functions
- Backwards compatibility was retained for effectful code
- Her systems programmer friends were happy she had a
capability-safe language
- And she grew up to be a dinosaur pirate witch PL designer.
22
SLIDE 82 Conclusion
Our heroine added comonadic purity to her programming language:
- She had a sound semantics and a clean type theory
- Fusion worked for pure functions
- Backwards compatibility was retained for effectful code
- Her systems programmer friends were happy she had a
capability-safe language
- And she grew up to be a dinosaur pirate witch PL designer.
22
SLIDE 83 Conclusion
Our heroine added comonadic purity to her programming language:
- She had a sound semantics and a clean type theory
- Fusion worked for pure functions
- Backwards compatibility was retained for effectful code
- Her systems programmer friends were happy she had a
capability-safe language
- And she grew up to be a dinosaur pirate witch PL designer.
22
SLIDE 84 Conclusion
Our heroine added comonadic purity to her programming language:
- She had a sound semantics and a clean type theory
- Fusion worked for pure functions
- Backwards compatibility was retained for effectful code
- Her systems programmer friends were happy she had a
capability-safe language
- And she grew up to be a dinosaur pirate witch PL designer.
22