Resugaring: Lifting Evaluation Sequences through Syntactic Sugar

Justin Pombrio, Shriram Krishnamurthi Brown University Syntactic Sugar

2 Syntactic Sugar x + 2 desugar x.__add__(2)

3 Syntactic Sugar x + 2 desugar x.__add__(2)

[x*x | x <- lst] desugar map (\x -> x*x) lst

4 Syntactic Sugar x + 2 desugar x.__add__(2)

[x*x | x <- lst] desugar map (\x -> x*x) lst x or y desugar let t = x in if t then t else y

5 Surface language (what you ) Syntactic Sugar x + 2 desugar x.__add__(2)

[x*x | x <- lst] desugar map (\x -> x*x) lst

x or y desugar let t = x in if t then t else y

6 Surface language Core language (what you write) Syntactic Sugar (what runs) x + 2 desugar x.__add__(2)

[x*x | x <- lst] desugar map (\x -> x*x) lst

x or y desugar let t = x in if t then t else y

7 Big surface language

desugar

Small core

8 Big surface language

desugar

Small core

9 Big surface language

desugar Code analyzer Small core Refactoring engine

Evaluator

10 Big surface language

desugar Code analyzer Small core Refactoring engine

Evaluator

11 Big surface language

desugar Code analyzer Small core Refactoring engine

Evaluator

12 Surface Core not(true) or true

13 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

14 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

let t = false in if t then t else true

if false then false else true

true

15 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

false or true let t = false in if t then t else true

if false then false else true

true true

16 Confection core eval → surface eval seq

17 18 92 steps

19 92 steps

7 steps

20 Surface Core not(true) or true

Resugaring: Running sugar in reverse

21 Surface Core not(true) or true

false or true

true

Resugaring: Running sugar in reverse

22 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

false or true

true

Resugaring: Running sugar in reverse

23 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

false or true let t = false in if t then t else true

if false then false else true

true true

Resugaring: Running sugar in reverse

24 Surface Core desugar not(true) or true let t = not(true) in resugar if t then t else true

false or true let t = false in if t then t else true

if false then false else true

true true

Resugaring: Running sugar in reverse

25 Surface Core desugar not(true) or true let t = not(true) in resugar if t then t else true

resugar false or true let t = false in if t then t else true

if false then false else true

true true

Resugaring: Running sugar in reverse

26 Surface Core desugar not(true) or true let t = not(true) in resugar if t then t else true

resugar false or true let t = false in if t then t else true

resugar if false then false else true

true true

Resugaring: Running sugar in reverse

27 THREE KEY PROPERTIES OF RESUGARING

28 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

resugar let t = false in if t then t else true

29 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

true or true resugar let t = false in or true if t then t else true

30 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

true or true resugar let t = false in or true if t then t else true

Emulation

Each surface term must desugar to the core term it purports to represent

31 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

resugar let t = false in if t then t else true

32 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

let t = false in resugar let t = false in if t then t else true if t then t else true

33 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

let t = false in resugar let t = false in if t then t else true if t then t else true

Abstraction

Show things in terms of a sugar precisely when the programmer used that sugar.

34 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

resugar let t = false in if t then t else true

resugar if false then false else true

resugar true true

35 Surface Core desugar not(true) or true let t = not(true) in if t then t else true

Coverage resugar let t = false in if t then t else true Show as many steps resugar as possible if false then false else true

resugar true true

36 x or y -> let t = x in if t then t else y

37 match substitute x or y -> let t = x in if t then t else y

38 expand match substitute x or y -> let t = x in if t then t else y substitute match

unexpand

39 A Little Theory

expand : Surf Term → Core Term unexpand : Core Term × Surf Term → Surf Term

40 A Little Theory

expand : Surf Term → Core Term unexpand : Core Term × Surf Term → Surf Term It's a lens!

41 A Little Theory

expand : Surf Term → Core Term unexpand : Core Term × Surf Term → Surf Term It's a lens! unexpand (expand T) T = T GetPut GetPut expand (unexpand T’ T) = T’ PutGet PutGet

42 A Little Theory

expand : Surf Term → Core Term unexpand : Core Term × Surf Term → Surf Term It's a lens! unexpand (expand T) T = T GetPut GetPut expand (unexpand T’ T) = T’ PutGet PutGet

Well-formedness criteria on rules ensure these laws.

43 Key Properties

Emulation

Abstraction

Coverage

44 Key Properties

Emulation (Lens Laws)

Abstraction

Coverage

45 Key Properties

Emulation (Lens Laws)

Abstraction (Tagging – see paper)

Coverage

46 Key Properties

Emulation (Lens Laws)

Abstraction (Tagging – see paper)

Coverage (Empirical)

47 A WORKING SYSTEM

48 92 steps

7 steps

49 Surface Core (or (and #t #f) #f)

50 Surface Core desugar (or (and #t #f) #f) ((lambda (t) (begin (if t t ((lambda () #f))))) (if #t ((lambda () #f)) #f))

51 Surface Core desugar (or (and #t #f) #f) ((lambda (t) (begin (if t t ((lambda () #f))))) (if #t ((lambda () #f)) #f))

((lambda (t) (begin (if t t ((lambda () #f))))) ((lambda () #f)))

((lambda (t) (begin (if t t ((lambda () #f))))) #f)

(if #f t ((lambda () #f)))

((lambda () #f))

#f

52 Surface Core desugar (or (and #t #f) #f) ((lambda (t) (begin (if t t ((lambda () #f))))) (if #t ((lambda () #f)) #f))

(or #f #f) ((lambda (t) (begin (if t t ((lambda () #f))))) ((lambda () #f)))

(or #f) ((lambda (t) (begin (if t t ((lambda () #f))))) #f)

(if #f t ((lambda () #f)))

((lambda () #f))

#f #f

53 Surface Core my-list = [2] cases(List) my-list: | empty() => print("empty") | (something, _) => print("not empty") end

54 Surface Core my-list = [2] desugar my-list = list.["link"](2, list.["empty"]) cases(List) my-list: block: | empty() => print("empty") tempMODRIOUJ :: List = my-list | link(something, _) => tempMODRIOUJ.["_match"]({ print("not empty") "empty" : fun(): print("empty") end, end "link" : fun(something, _): print("not empty") end }, fun(): raise("cases: no cases matched") end) end

55 Surface Core my-list = [2] desugar my-list = list.["link"](2, list.["empty"]) cases(List) my-list: block: | empty() => print("empty") tempMODRIOUJ :: List = my-list | link(something, _) => tempMODRIOUJ.["_match"]({ print("not empty") "empty" : fun(): print("empty") end, end "link" : fun(something, _): print("not empty") end }, fun(): raise("cases: no cases matched") end) end

my-list = obj.["link"](2, list.["empty"]) my-list = (2, []) block: block: tempMODRIOUJ :: List = my-list tempMODRIOUJ :: List = my-list tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") "link" : fun(something, _): print("not empty") end}, fun(): end, "link" : fun(something, _): print("not empty") end}, raise("cases: no cases matched") end) fun(): raise("cases: no cases matched") end) end end

my-list = obj.["link"](2, list.["empty"]) my-list = [2] block: block: tempMODRIOUJ :: List = my-list tempMODRIOUJ :: List = my-list tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") "link" : fun(something, _): print("not empty") end}, fun(): end, "link" : fun(something, _): print("not empty") end}, raise("cases: no cases matched") end) fun(): raise("cases: no cases matched") end) end end

my-list = (2, list.["empty"]) tempMODRIOUJ :: List = [2] block: tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") tempMODRIOUJ :: List = my-list end, "link" : fun(something, _): print("not empty") end}, tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, fun(): raise("cases: no cases matched") end) "link" : fun(something, _): print("not empty") end}, fun(): raise("cases: no cases matched") end) [2].["_match"]({"empty" : fun(): print("empty") end, end "link" : fun(something, _): print("not empty") end}, fun(): raise("cases: no cases matched") end) my-list = (2, obj.["empty"]) block: [2].["_match"]({"empty" : fun(): print("empty") end, tempMODRIOUJ :: List = my-list "link" : fun(something, _): print("not empty") end}, fun(): tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, raise("cases: no cases matched") end) "link" : fun(something, _): print("not empty") end}, fun(): raise("cases: no cases matched") end) ({"empty" : fun(): end, "link" : fun(something, _): end print("not empty") end}, fun(): raise("cases: no cases matched") end) my-list = (2, obj.["empty"]) block: ({"empty" : fun(): end, "link" : fun(): end}, fun(): tempMODRIOUJ :: List = my-list raise("cases: no cases matched") end) tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, "link" : fun(something, _): print("not empty") end}, fun(): (obj, fun(): raise("cases: no cases matched") end) raise("cases: no cases matched") end) end (obj, fun(): end) ("not empty")

"not empty" 56 Surface Core my-list = [2] desugar my-list = list.["link"](2, list.["empty"]) cases(List) my-list: block: | empty() => print("empty") tempMODRIOUJ :: List = my-list | link(something, _) => tempMODRIOUJ.["_match"]({ print("not empty") "empty" : fun(): print("empty") end, end "link" : fun(something, _): print("not empty") end my-list = [2] }, cases(List) my-list: fun(): raise("cases: no cases matched") end) | empty() => print("empty") end my-list = obj.["link"](2, list.["empty"]) my-list = (2, []) | link(something, _) => block: block: tempMODRIOUJ :: List = my-list tempMODRIOUJ :: List = my-list tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") print("not empty") "link" : fun(something, _): print("not empty") end}, fun(): end, "link" : fun(something, _): print("not empty") end}, raise("cases: no cases matched") end) fun(): raise("cases: no cases matched") end) end end end my-list = obj.["link"](2, list.["empty"]) my-list = [2] block: block: tempMODRIOUJ :: List = my-list tempMODRIOUJ :: List = my-list tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") cases(List) [2]: "link" : fun(something, _): print("not empty") end}, fun(): end, "link" : fun(something, _): print("not empty") end}, raise("cases: no cases matched") end) fun(): raise("cases: no cases matched") end) | empty() => print("empty") end end my-list = (2, list.["empty"]) tempMODRIOUJ :: List = [2] | link(something, _) => block: tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") tempMODRIOUJ :: List = my-list end, "link" : fun(something, _): print("not empty") end}, tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, fun(): raise("cases: no cases matched") end) print("not empty") "link" : fun(something, _): print("not empty") end}, fun(): raise("cases: no cases matched") end) [2].["_match"]({"empty" : fun(): print("empty") end, end end "link" : fun(something, _): print("not empty") end}, fun(): raise("cases: no cases matched") end) my-list = (2, obj.["empty"]) block: [2].["_match"]({"empty" : fun(): print("empty") end, tempMODRIOUJ :: List = my-list "link" : fun(something, _): print("not empty") end}, fun(): ("not empty") tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, raise("cases: no cases matched") end) "link" : fun(something, _): print("not empty") end}, fun(): raise("cases: no cases matched") end) ({"empty" : fun(): end, "link" : fun(something, _): end print("not empty") end}, fun(): raise("cases: no cases matched") end) my-list = (2, obj.["empty"]) block: ({"empty" : fun(): end, "link" : fun(): end}, fun(): tempMODRIOUJ :: List = my-list raise("cases: no cases matched") end) "not empty" tempMODRIOUJ.["_match"]({"empty" : fun(): print("empty") end, "link" : fun(something, _): print("not empty") end}, fun(): (obj, fun(): raise("cases: no cases matched") end) raise("cases: no cases matched") end) end (obj, fun(): end) ("not empty")

"not empty" 57 1. Redex semantics engineering tool

2. Racket (racket.org)

3. Pyret (pyret.org)

58 t in y u rl Resugaring .c o m / The Confection tool re s ● u Declarative sugar specification g a ● Language agnostic r ● Guarantees Emulation and Abstraction

Current/future work ● Hygiene! ● Better error messages through resugaring

59 60 BACKUP SLIDES

61 How should this resugar? let t = not(true) in if t then t else true

Looks like the desugaring of an ‘or’.

But maybe the user wrote it?

62 Surface Core desugar or not(true) or false let t = not(true) in if t then t else false

resugar or false or false let t = false in if t then t else false

resugar if false then false else false

resugar false false

63 Surface Core desugar s1 c1

resugar c2 How do we this transparent? resugar s2 c3 … … val val

64 Reconstruct the (core) program as source each evaluation step (along with its tags).

Either instrument the compiler or do a pre-compilation step

CPS/ANF makes this easier; or just statefully keep track of the stack

65 Max([]) -> Raise(“empty list”);

Max(xs) -> MaxAcc(xs, -infinity);

Surface Max2 Core

Max([-infinity]) desugar MaxAcc([-infinity], -infinity)

Max2 Max([]) resugar MaxAcc([], -infinity)

66 Related Work

• J. Hennessy. Symbolic debugging of optimized code. Transactions on Programming Languages and Systems, 4(3), 1982. • J. Clements, M. Flatt, and M. Felleisen. Modeling an algebraic stepper. In European Symposium on Programming Languages and Systems, 2001. • R. Perera, U. A. Acar, J. Cheney, and P. B. Levy. Functional programs that explain their work. In International Conference on Functional Programming, 2012 • A. V. Deursen, P. Klint, and F. Tip. Origin tracking. Journal of Symbolic Computation, 15(5–6), 1993.

67