6.001 SICP Variations on a Scheme

Slides:



Advertisements
Similar presentations
Scheme in Scheme. Why implement Scheme in Scheme  Implementing a language is a good way to learn more about programming languages  Interpreters are.
Advertisements

Assignments and Procs w/Params EOPL3 Chapter 4. Expressible vs. Denotable values Expressible Values –the language can express and compute these –represented.
1 Programming Languages (CS 550) Lecture Summary Functional Programming and Operational Semantics for Scheme Jeremy R. Johnson.
Functional Programming. Pure Functional Programming Computation is largely performed by applying functions to values. The value of an expression depends.
1 The metacircular evaluator Names Extend the calculator to store intermediate results as named values (define x (+ 4 5)) store result as x (+ x.
Metacircular Evaluation SICP Chapter 4 Mark Boady.
מבוא מורחב למדעי המחשב תרגול 13 Lazy Evaluation. Lazy Evaluation Implementation Two types of values – Delayed Values – Actual Values Introduce two functions.
1 The Metacircular Evaluator Chapter 4 Section 4.1 We will not cover every little detail in the lectures, so you MUST read Section 4.1 in full.
1 Lecture 18 Continue Evaluator. 2 z9 true#t + twice Representing procedures (eval '(define twice (lambda (x) (+ x x))) GE) symbol primitive scheme procedure.
6.001 SICP 1 Normal (Lazy) Order Evaluation Memoization Streams.
Functional programming: LISP Originally developed for symbolic computing Main motivation: include recursion (see McCarthy biographical excerpt on web site).
SICP Interpretation part 1 Parts of an interpreter Arithmetic calculator Names Conditionals and if Store procedures in the environment.
Lexical vs. Dynamic Scope …where do I point to?. Intro… Remember in Scheme whenever we call a procedure we pop a frame and point it to where the procedure.
6.001 SICP SICP – Evaluation I Recitation 11/19/2004 Eval review Evaluation examples define lambda apply New language elements.
SICP Variations on a Scheme Scheme Evaluator – A Grand Tour Techniques for language design: Interpretation: eval/apply Semantics vs. syntax Syntactic.
SICP Infinite streams – using lazy evaluation Beyond Scheme – designing language variants: Streams – an alternative programming style!
Functional programming: LISP Originally developed for symbolic computing First interactive, interpreted language Dynamic typing: values have types, variables.
SICP Variations on a Scheme (2) Beyond Scheme – designing language variants: Lazy evaluation Complete conversion – normal order evaluator Upward.
1 The metacircular evaluator (Cont.) Defining new procedures (define (lambda? e) (tag-check e 'lambda)) (define (eval exp env) (cond ((number? exp)
SICP Interpretation Parts of an interpreter Arithmetic calculator Names Conditionals and if Storing procedures in the environment Environment as.
1 The Evaluator. 2 Compiler vs. Interpreter Command Processing Unit The Computer Program in Low Level Machine Language Program in High Level Language.
1 Lecture 20 Lazy Evaluation Continued (4.2.1, 4.2.2) MC-eval examples from exams (Time permitting)
1 Read-Eval-Print Loop (define (driver-loop) (prompt-for-input input-prompt) (let ((input (read))) (let ((output (eval input the-global-env))) (announce-output.
1/33 Basic Scheme February 8, 2007 Compound expressions Rules of evaluation Creating procedures by capturing common patterns.
1 Programming Languages (CS 550) Lecture 4 Summary Functional Programming and Operational Semantics for Scheme Jeremy R. Johnson.
Fall 2008Programming Development Techniques 1 Topic 17 Assignment, Local State, and the Environment Model of Evaluation Section 3.1 & 3.2.
1 Lecture 19 Dynamic Scoping Lazy Evaluation (4.2.1, 4.2.2)
1/ SICP Variations on a Scheme Scheme Evaluator – A Grand Tour Making the environment model concrete Defining eval defines the language –Provides.
6.037 Lecture 7B Scheme Variants Normal Order Lazy Evaluation Streams Edited by Mike Phillips & Ben Vandiver Original Material by Eric Grimson & Duane.
SICP Interpretation part 2 Store operators in the environment Environment as explicit parameter Defining new procedures.
CSE 341 Lecture 21 delayed evaluation; thunks; streams slides created by Marty Stepp
(Thunking about Thunks)
Operational Semantics of Scheme
Basic Scheme February 8, 2007 Compound expressions Rules of evaluation
Edited by Original material by Eric Grimson
6.001 SICP Object Oriented Programming
6.001 SICP Compilation Context: special purpose vs. universal machines
The role of abstractions
September 4, 1997 Programming Languages (CS 550) Lecture 6 Summary Operational Semantics of Scheme using Substitution Jeremy R. Johnson TexPoint fonts.
Closures and Streams cs784(Prasad) L11Clos
Env. Model Implementation
Original material by Eric Grimson
Nondeterministic Evaluation
6.001 SICP Data abstractions
Streams Sections 3.5.1,3.5.2 Pages
The Metacircular Evaluator
Lecture 18 Infinite Streams and
Dynamic Scoping Lazy Evaluation
The Metacircular Evaluator
6.001 SICP Environment model
Lecture 24: Metalinguistics CS200: Computer Science
6.001 SICP Streams – the lazy way
The Metacircular Evaluator (Continued)
Lecture 27: In Praise of Idleness CS200: Computer Science
Lecture 26: The Metacircular Evaluator Eval Apply
6.001 SICP Further Variations on a Scheme
Streams, Delayed Evaluation and a Normal Order Interpreter
Streams and Lazy Evaluation in Lisp and Scheme
Lecture 12: Message passing The Environment Model
Lecture 13 - Assignment and the environments model Chapter 3
6.001 SICP Explicit-control evaluator
6.001 SICP Explicit-control evaluator
6.001 SICP Variations on a Scheme
6.001 SICP Interpretation Parts of an interpreter
Assignments and Procs w/Params
Introduction to the Lab
topics interpreters meta-linguistic abstraction eval and apply
Rehearsal: Lazy Evaluation Infinite Streams in our lazy evaluator
Lecture 25: The Metacircular Evaluator Eval Apply
Presentation transcript:

6.001 SICP Variations on a Scheme Beyond Scheme – designing language variants: Lazy evaluation Complete conversion – normal order evaluator Upward compatible extension – lazy, lazy-memo

Stages of an interpreter input to each stage Lexical analyzer "(average 4 (+ 5 5))" ( average 4 + 5 ) Parser Evaluator Environment 4 symbol average 5 symbol + Printer 7 "7"

Evaluation model Rules of evaluation: If expression is self-evaluating (e.g. a number), just return value If expression is a name, look up value associated with that name in environment If expression is a lambda, create procedure and return If expression is special form (e.g. if) follow specific rules for evaluating subexpressions If expression is a compound expression Evaluate subexpressions in any order If first subexpression is primitive (or built-in) procedure, just apply it to values of other subexpressions If first subexpression is compound procedure (created by lambda), evaluate the body of the procedure in a new environment, which extends the environment of the procedure with a new frame in which the procedure’s parameters are bound to the supplied arguments

Normal Order (Lazy) Evaluation Alternative models for computation: Applicative Order: evaluate all arguments, then apply operator Normal Order: go ahead and apply operator with unevaluated argument subexpressions evaluate a subexpression only when value is needed to print by primitive procedure (that is, primitive procedures are "strict" in their arguments)

Applicative Order Example (define (foo x) (write-line "inside foo") (+ x x)) (foo (begin (write-line "eval arg") 222)) Evaluating argument to foo => (begin (write-line “eval arg”) 222) => 222 Evaluating second part of argument => (begin (write-line "inside foo") (+ 222 222)) From body of foo We first evaluated argument, then substituted value into the body of the procedure eval arg inside foo => 444

Normal Order Example (define (foo x) (write-line "inside foo") (+ x x)) (foo (begin (write-line "eval arg") 222)) From body of foo => (begin (write-line "inside foo") (+ (begin (w-l "eval arg") 222) (begin (w-l "eval arg") 222))) As if we substituted the unevaluated expression in the body of the procedure inside foo eval arg eval arg => 444

Normal order (lazy evaluation) versus applicative order How can we change our evaluator to use normal order? Create “delayed objects” – expressions whose evaluation has been deferred Change the evaluator to force evaluation only when needed Why is normal order useful? What kinds of computations does it make easier?

How can we implement lazy evaluation? (define (l-apply procedure arguments env) ; changed (cond ((primitive-procedure? procedure) (apply-primitive-procedure procedure (list-of-arg-values arguments env))) ((compound-procedure? procedure) (l-eval-sequence (procedure-body procedure) (extend-environment (procedure-parameters procedure) (list-of-delayed-args arguments env) (procedure-environment procedure)))) (else (error "Unknown proc" procedure))))

Lazy Evaluation – l-eval Most of the work is in l-apply; need to call it with: actual value for the operator just expressions for the operands the environment... (define (l-eval exp env) (cond ((self-evaluating? exp) exp) ... ((application? exp (l-apply (actual-value (operator exp) env) (operands exp) env)) (else (error "Unknown expression" exp))))

Meval versus L-Eval (define (meval exp env) (cond ((self-evaluating? exp) exp) … ((cond? exp) (meval (cond->if exp) env)) ((application? exp) (mapply (meval (operator exp) env) (list-of-values (operands exp) env))) (else (error "Unknown expression type -- EVAL" exp)))) (define (l-eval exp env) (cond ((self-evaluating? exp) exp) ... ((cond? Exp) ((application? exp (l-apply (actual-value (operator exp) env) (operands exp) env)) (else (error "Unknown expression" exp))))

Actual vs. Delayed Values (define (actual-value exp env) (force-it (l-eval exp env))) (define (list-of-arg-values exps env) (if (no-operands? exps) '() (cons (actual-value (first-operand exps) env) (list-of-arg-values (rest-operands exps) env)))) Used when applying a primitive procedure (define (list-of-delayed-args exps env) (if (no-operands? exps) '() (cons (delay-it (first-operand exps) env) (list-of-delayed-args (rest-operands exps) env)))) Used when applying a compound procedure

Representing Thunks Abstractly – a thunk is a "promise" to return a value when later needed ("forced") Concretely – our representation: thunk env exp

Thunks – delay-it and force-it (define (delay-it exp env) (list 'thunk exp env)) (define (thunk? obj) (tagged-list? obj 'thunk)) (define (thunk-exp thunk) (cadr thunk)) (define (thunk-env thunk) (caddr thunk)) (define (force-it obj) (cond ((thunk? obj) (actual-value (thunk-exp obj) (thunk-env obj))) (else obj))) (define (actual-value exp env) (force-it (l-eval exp env)))

Memo-izing evaluation In lazy evaluation, if we reuse an argument, have to reevaluate each time In normal evaluation, argument is evaluated once, and just referenced Can we keep track of values once we’ve obtained them, and avoid cost of reevaluation?

Memo-izing Thunks Idea: once thunk exp has been evaluated, remember it If value is needed again, just return it rather than recompute thunk env exp Concretely – mutate a thunk into an evaluated-thunk evaluated- thunk result

Thunks – Memoizing Implementation (define (evaluated-thunk? obj) (tagged-list? obj 'evaluated-thunk)) (define (thunk-value evaluated-thunk) (cadr evaluated-thunk)) (define (force-it obj) (cond ((thunk? obj) (let ((result (actual-value (thunk-exp obj) (thunk-env obj)))) (set-car! obj 'evaluated-thunk) (set-car! (cdr obj) result) (set-cdr! (cdr obj) '()) result)) ((evaluated-thunk? obj) (thunk-value obj)) (else obj)))

Lazy Evaluation – other changes needed Example – need actual predicate value in conditional if... (define (l-eval-if exp env) (if (true? (actual-value (if-predicate exp) env)) (l-eval (if-consequent exp) env) (l-eval (if-alternative exp) env))) Example – don't need actual value in assignment... (define (l-eval-assignment exp env) (set-variable-value! (assignment-variable exp) (l-eval (assignment-value exp) env) env) 'ok)

Laziness and Language Design We have a dilemma with lazy evaluation Advantage: only do work when value actually needed Disadvantages not sure when expression will be evaluated; can be very big issue in a language with side effects may evaluate same expression more than once Memoization doesn't fully resolve our dilemma Advantage: Evaluate expression at most once Disadvantage: What if we want evaluation on each use? Alternative approach: give programmer control!

Variable Declarations: lazy and lazy-memo Handle lazy and lazy-memo extensions in an upward-compatible fashion.; (lambda (a (b lazy) c (d lazy-memo)) ...) "a", "c" are normal variables (evaluated before procedure application "b" is lazy; it gets (re)-evaluated each time its value is actually needed "d" is lazy-memo; it gets evaluated the first time its value is needed, and then that value is returned again any other time it is needed again.

Syntax Extensions – Parameter Declarations (define (first-variable var-decls) (car var-decls)) (define (rest-variables var-decls) (cdr var-decls)) (define declaration? pair?) (define (parameter-name var-decl) (if (pair? var-decl) (car var-decl) var-decl)) (define (lazy? var-decl) (and (pair? var-decl) (eq? 'lazy (cadr var-decl)))) (define (memo? var-decl) (and (pair? var-decl) (eq? 'lazy-memo (cadr var-decl))))

Controllably Memo-izing Thunks thunk – never gets memoized thunk-memo – first eval is remembered evaluated-thunk – memoized-thunk that has already been evaluated thunk- memo env exp when forced evaluated- thunk result

A new version of delay-it Look at the variable declaration to do the right thing... (define (delay-it decl exp env) (cond ((not (declaration? decl)) (l-eval exp env)) ((lazy? decl) (list 'thunk exp env)) ((memo? decl) (list 'thunk-memo exp env)) (else (error "unknown declaration:" decl))))

Change to force-it (define (force-it obj) (cond ((thunk? obj) ;eval, but don't remember it (actual-value (thunk-exp obj) (thunk-env obj))) ((memoized-thunk? obj) ;eval and remember (let ((result (thunk-env obj)))) (set-car! obj 'evaluated-thunk) (set-car! (cdr obj) result) (set-cdr! (cdr obj) '()) result)) ((evaluated-thunk? obj) (thunk-value obj)) (else obj)))

Changes to l-apply Key: in l-apply, only delay "lazy" or "lazy-memo" params make thunks for "lazy" parameters make memoized-thunks for "lazy-memo" parameters (define (l-apply procedure arguments env) (cond ((primitive-procedure? procedure) ...) ; as before; apply on list-of-arg-values ((compound-procedure? procedure) (l-eval-sequence (procedure-body procedure) (let ((params (procedure-parameters procedure))) (extend-environment (map parameter-name params) (list-of-delayed-args params arguments env) (procedure-environment procedure))))) (else (error "Unknown proc" procedure))))

Deciding when to evaluate an argument... Process each variable declaration together with application subexpressions – delay as necessary: (define (list-of-delayed-args var-decls exps env) (if (no-operands? exps) '() (cons (delay-it (first-variable var-decls) (first-operand exps) env) (list-of-delayed-args (rest-variables var-decls) (rest-operands exps) env))))

Summary Lazy evaluation – control over evaluation models Convert entire language to normal order Upward compatible extension lazy & lazy-memo parameter declarations

Streams – a different way of structuring computation Imagine simulating the motion of an object Use state variables, clock, equations of motion to update State of the simulation captured in instantaneous values of state variables position: elasticity: Wall: position: velocity: Ball: time: clock:

Streams – a different way of structuring computation OR – have each object output a continuous stream of information State of the simulation captured in the history (or stream) of values x t Think about computation as if had entire sequence of values available at any point y t

How do we use this new lazy evaluation? Our users could implement a stream abstraction: (define (cons-stream x (y lazy-memo)) (lambda (msg) (cond ((eq? msg 'stream-car) x) ((eq? msg 'stream-cdr) y) (else (error "unknown stream msg" msg))))) (define (stream-car s) (s 'stream-car)) (define (stream-cdr s) (s 'stream-cdr)) OR (define (cons-stream x (y lazy-memo) (cons x y)) (define stream-car car) (define stream-cdr cdr)

Stream Object A pair-like object, except the cdr part is lazy (not evaluated until needed): a thunk-memo value stream-car cons-stream stream-cdr Example (define x (cons-stream 99 (/ 1 0))) (stream-car x) => 99 (stream-cdr x) => error – divide by zero

Decoupling computation from description Can separate order of events in computer from apparent order of events in procedure description (list-ref (filter (lambda (x) (prime? x)) (enumerate-interval 1 100000000)) 100) (define (stream-interval a b) (if (> a b) the-empty-stream (cons-stream a (stream-interval (+ a 1) b)))) (stream-ref (stream-filter (lambda (x) (prime? x)) (stream-interval 1 100000000)) 100)

Some details on stream procedures (define (stream-filter pred str) (if (pred (stream-car str)) (cons-stream (stream-car str) (stream-filter pred (stream-cdr str))) (stream-cdr str))))

Decoupling order of evaluation (stream-filter prime? (str-in 1 100000000)) (st-in 2 10000000) 1 (stream-filter prime? ) (stream-filter prime? ) (st-in 2 10000000) (stream-filter prime? ) (st-in 3 10000000) 2 (stream-filter prime? (stream-cdr 2

Result: Infinite Data Structures! Some very interesting behavior (define ones (cons-stream 1 ones)) (stream-car (stream-cdr ones)) => 1 1 ones The infinite stream of 1's! ones: 1 1 1 1 1 1 .... Compare: (define ones (cons 1 ones)) => error, ones undefined

Finite list procs turn into infinite stream procs (define (add-streams s1 s2) (cond ((null? s1) '()) ((null? s2) '()) (else (cons-stream (+ (stream-car s1) (stream-car s2)) (add-streams (stream-cdr s1) (stream-cdr s2)))))) (define ints (cons-stream 1 (add-streams ones ints))) ones: 1 1 1 1 1 1 .... ints: 1 2 3 ... add-streams ones ints add-streams (str-cdr ones) (str-cdr ints)

Finding all the primes XX 7 XX 5 XX X 2 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 60 69 68 67 66 65 64 63 62 61 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 XX X 3

Building a sieve? (define (sieve str) (cons-stream (stream-car str) (sieve (stream-filter (lambda (x) (not (divisible? X (stream-car str)))) (stream-cdr str))))) (define primes (sieve (stream-cdr ints))) (2 (sieve (filter ints 2))) (2 3 (sieve (filter (sieve (filter ints 2)) 3)))

Streams Programming Signal processing: Streams model: x[n] y[n] Delay + Streams model: add-streams stream- scale x y cdr G

Integration as an example (define (integral integrand init dt) (define int (cons-stream init (add-streams (stream-scale dt integrand) int))) int) (integral ones 0 2) => 0 2 4 6 8 Ones: 1 1 1 1 1 Scale 2 2 2 2 2

Summary We can control when arguments are evaluated By making a lazy evaluator By changing the evaluator to allow specification of arguments Changing the evaluator requires a small amount of work but dramatically shifts the behavior of the system Applicative order versus Normal order Using a lazy evaluator lets us separate the apparent order of computation inherent in a problem from the actual order of evaluation inside the machine