Evaluation Contexts First-Class Continuations Lambda Interpreters

Evaluation Contexts First-Class Continuations Lambda Interpreters

Gimme A Break (from types) We have more to do with type systems: Parametric Polymorphism CS152: Programming Languages Recursive Types Type-And-Effect Systems Lecture 14 — But sometimes it’s more fun to mix up the lecture schedule Evaluation Contexts First-Class Continuations This lecture: Related topics that work in typed or untyped settings: Lambda Interpreters Continuation-Passing Style How operational semantics can be defined more concisely How lambda-calculus (or PLs) can be enriched with powerful control operators How lambda-calculus can be efficiently implemented Dan Grossman Cool programming idioms related to these concepts Spring 2011 Dan Grossman CS152 Spring 2011, Lecture 14 2 Toward Evaluation Contexts Evaluation Contexts λ-calculus with extensions has many “boring inductive rules”: Define evaluation contexts, which are expressions with one hole where “interesting work” is allowed to occur: e1 → e1 e2 → e2 e → e e → e E ::= [·] | Ee| vE| (E, e) | (v, E) | E.1 | E.2 e1 e2 → e1 e2 ve2 → ve2 A(e) → A(e ) B(e) → B(e ) | A(E) | B(E) | (match E with Ax. e | By. e ) 1 2 e1 → e1 e2 → e2 e → e e → e Define “filling the hole” E[e] in the obvious way (stapling) (e1,e2) → (e1,e2) (v1,e2) → (v1,e2) e.1 → e .1 e.2 → e .2 A metafunction of type EvalContext→Exp→Exp e → e Semantics: Use two judgments match e with Ax. e1 | By. e2 → match e with Ax. e1 | By. e2 p e → e e → e with 1 rule: And some “interesting do-work rules”: E[e] → E[e] p e → e (λx. e) v → e[v/x] (v1,v2).1 → v1 (v1,v2).2 → v2 with all the “interesting work”: p p p (λx. e) v → e[v/x] (v1,v2).1 → v1 (v1,v2).2 → v2 match A(v) with Ax. e | By. e → e [v/x] 1 2 1 p match A(v) with Ax. e1 | By. e2 → e1[v/x] match B(v) with Ay. e1 | Bx. e2 → e2[v/x] p match B(v) with Ay. e1 | Bx. e2 → e2[v/x] Dan Grossman CS152 Spring 2011, Lecture 14 3 Dan Grossman CS152 Spring 2011, Lecture 14 4 Decomposition Evaluation Contexts: So what? Evaluation relies on decomposition (unstapling the correct subtree) Small-step semantics (old) and evaluation-context semantics (new) p are very similar: Given e,findE, ea, ea such that e = E[ea] and ea → ea Totally equivalent step sequence (made both left-to-right call-by-value) Theorem (Unique Decomposition): There is at most one decomposition of e Just rearranged things to be more concise: Each boring rule became a form of E Hence evaluation is deterministic since at most one primitive step can apply to any expression Both “work” the same way: Find the next place in the program to take a “primitive step” Take that step e Theorem (Progress, restated): If is well-typed, then there is a Plug the result into the rest of the program decomposition or e is a value Repeat (next “primitive step” could be somewhere else) until you can’t anymore (value or stuck) Evaluation contexts so far just cleanly separate the “find and plug” from the “take that step” by building an explicit E Dan Grossman CS152 Spring 2011, Lecture 14 5 Dan Grossman CS152 Spring 2011, Lecture 14 6 Continuations Examples (exceptions-like) Now that we have defined E explicitly in our metalanguage,what if we also put it on our language 1+(letcc k. 2+3)→∗ 6 From metalanguage to language is called reification 1+(letcc k. 2+(throw k 3)) →∗ 4 First-class continuations in one slide: e ::= ...| letcc x. e | throw ee| cont E 1+(letcc k. (throw k (2 + 3))) →∗ 6 v ::= ...| cont E E ::= ...| throw Ee| throw vE 1+(letcc k. (throw k (throw k (throw k 2)))) →∗ 3 E[letcc x. e] → E[(λx. e)(cont E)] E[throw (cont E) v] → E[v] p New operational rules for → not → because “the E matters” letcc x. e grabs the current evaluation context (“the stack”) throw (cont E) v restores old context: “jump somewhere” cont E not in source programs: “saved stack (value)” Dan Grossman CS152 Spring 2011, Lecture 14 7 Dan Grossman CS152 Spring 2011, Lecture 14 8 Example (“time travel”) Is this useful? Caml doesn’t have first-class continuations, but if it did: First-class continuations are a single construct sufficient for: let valOf x = match x with None-> failwith "" |Some x-> x Exceptions let x = ref true (* avoids infinite loop ) let g = ref None Cooperative threads (including coroutines) lety=ref(1+2+(letcc k. (g := Some k); 3)) “yield” captures the continuation (the “how to resume me”) letz=if!x and gives it to the scheduler (implemented in the language), then (x := false; throw (valOf (!g)) 7) which then throws to another thread’s “how to resume me” else !y Other crazy things SML/NJ does: This runs and binds 10 to z: Often called the “goto of functional programming” — incredibly powerful, but nonstandard uses are usually open SMLofNJ.Cont inscrutable val x = ref true (* avoids infinite loop *) Key point is that we can “jump back in” unlike boring-old val g : int cont option ref = ref NONE exceptions valy=ref(1+2+(callcc (fn k => ((g := SOME k); 3)))) valz=if!xthen (x := false; throw (valOf (!g)) 7) else !y Dan Grossman CS152 Spring 2011, Lecture 14 9 Dan Grossman CS152 Spring 2011, Lecture 14 10 Another view Where are we If you’re confused, think call stacks: Done: Redefined our operational semantics using evaluation contexts What if your favorite language had operations for: That made it easy to define first-class continuations Store current stack in x Replace current stack with stack in x Example uses of continuations Now: “Resume the stack’s hole” with something different or when Implement an efficent lambda-calculus interpreter using little mutable state is different more than malloc and a single while-loop Else you are sure to have an infinite loop since you will later Explicit evaluation contexts (i.e., continuations) is essential resume the stack again Key novelty is maintaining the current context incrementally letcc and throw can be O(1) operations (homework 4) Can achieve the same effect as a source-to-source transformation The continuation-passing-style (CPS) transformation Programming in explicit CPS-style is also a powerful idiom Dan Grossman CS152 Spring 2011, Lecture 14 11 Dan Grossman CS152 Spring 2011, Lecture 14 12 See the code Example See lec14_interp.ml for four interpreters where each is: Small-step (first interpreter): More efficient than the previous one and relies on less from the meta-language Close enough to the previous one that equivalence among them is tractable to prove The interpreters: 1. Plain-old small-step with substitution Decomposition (second interpreter): 2. Evaluation contexts, re-decomposing at each step 3. Incremental decomposition, made efficient by representing evaluation contexts (i.e., continuations) as a linked list with “shallow end” of the stack at the beginning of the list 4. Replacing substitution with environments The last interpreter is trivial to port to assembly or C Dan Grossman CS152 Spring 2011, Lecture 14 13 Dan Grossman CS152 Spring 2011, Lecture 14 14 Example Example Decomposition (second interpreter): Decomposition rewritten with linked list (hole implicit at front): Some loop iterations of third interpreter: Decomposition rewritten with linked list (hole implicit at front): Fourth interpreter: replace substitution with environment/closures Dan Grossman CS152 Spring 2011, Lecture 14 15 Dan Grossman CS152 Spring 2011, Lecture 14 16 The end result Toward the CPS transform The last interpreter needs just: Rather than a fancy abstract machine where... Aloop The metalanguage doesn’t need recursion (a call-stack) Lists for contexts and environments letcc/throw are easy-to-encode O(1) operations Tag tests ... can achieve the same effect via a whole-program translation Moreover: into a sublanguage (source-to-source transformation) Function calls execute in O(1) time No expressions with nontrivial evaluation contexts Variable look-ups don’t, but that’s fixable Every expression becomes a continuation-accepting function (e.g., de Bruijn indices and arrays for environments) Never “return” — instead call the current continuation Other operations, including pairs, conditionals, letcc, and throw also all work in O(1) time Need new kinds of contexts and values Last problem of homework 4 Making evaluation contexts explicit data structures was key Dan Grossman CS152 Spring 2011, Lecture 14 17 Dan Grossman CS152 Spring 2011, Lecture 14 18 The CPS transformation (one way to do it) Result of the CPS transformation A metafunction from expressions to expressions Correctness: e is equivalent to CPSE(e) λx. x If whole program has type τP and e has type τ ,then Example source language (other features similar): CPSE(e) has type (τ → τP ) → τP CPS (e) e e ::= x | λx. e | ee| c | e + e Fixes evaluation order: E will evaluate in left-to-right call-by-value v ::= x | λx. e | c Other similar transformations encode other evaluation orders Every intermediate computation is bound to a variable (helpful CPSE(v)=λk. k CPSV(v) for compiler writers) CPSE(e1 + e2)=λk. CPSE(e1) λx1. CPSE(e2) λx2.k(x1+x2) For all e, evaluation of CPSE(e) stays in this sublanguage: CPSE(e1 e2)=λk. CPSE(e1) λf. CPSE(e2) λx. f x k e ::= v | vv| vvv| v (v + v) CPSV(c)=c v ::= x | λx. e | c CPSV(x)=x Hence no need for a call-stack: every call is a tail-call CPSV(λx. e)=λx. λk.

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    4 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us