Normal Order (Lazy) Evaluation 6.001 SICP Alternative models for computation:

• Applicative Order: • Normal (Lazy) Order Evaluation • evaluate all arguments, then apply operator • • Streams •Normal Order: • pass unevaluated expression to function • evaluate only when needed for primitive operation

6.001 SICP 6.001 SICP 1 2

Applicative Order vs. Normal (Lazy) Order Applicative vs. Normal?

(define (foo x) (write-line "inside foo") (define (try a b) (+ x x)) (if (= a 0) 1 b))

(foo (begin (write-line "eval arg") 222)) (try 0 (/ 1 0))

(try 0 (pi-to-this-many-digits 10000000)) Applicative Order: Normal (Lazy) Order: eval arg inside foo inside foo eval arg => 444 eval arg => 444 We first evaluated argument, then substituted value into the As if we substituted the body of the procedure unevaluated expression in the 6.001 SICP 6.001 SICP body of the procedure 3 4

Exercise – Problem with Applicative Order How can we implement lazy evaluation?

Why cant we use define to define any special form? (define (l-apply procedure arguments env) ; changed (cond ((primitive-procedure? procedure) E.g write a procedure safe/ that only divide if b <> 0 (apply-primitive-procedure procedure (list-of-arg-values arguments env))) ((compound-procedure? procedure) (define (unless pred usual exc) (if pred exc usual)) (l-eval-sequence (procedure-body procedure) (define (safe/ a b) (extend-environment (unless (= b 0) (/ a b) 0) (procedure-parameters procedure) (define (safe/ a b) (list-of-delayed-args arguments env) (cond ((= b 0) 0) (procedure-environment procedure)))) (else (/ a b)))) (else (error "Unknown proc" procedure))))

6.001 SICP 6.001 SICP 5 6

1 – a delayed argument Thunks – delay-it and force-it

• Abstractly –a is a "promise" to return a value when (define (delay-it exp env) (list 'thunk exp env)) later needed ("forced") (define (thunk? obj) (tagged-list? obj 'thunk)) (define (thunk-exp thunk) (cadr thunk)) (define (thunk-env thunk) (caddr thunk)) • Concretely –our representation: (define (force-it obj) (cond ((thunk? obj) thunk exp env (actual-value (thunk-exp obj) (thunk-env obj))) (else obj))) (delay-it exp env) – promise to eval exp later (define (actual-value exp env) (force-it exp) – eval exp now, leave no thunks (force-it (l-eval exp env)))

6.001 SICP 6.001 SICP 7 8

Exercise – Applicative vers Normal Order Memo-izing evaluation

• Write a function normal-order? That returns # if the • In lazy evaluation an arg is reevaluate each time it is used language is normal order and #f if the language is • In applicative order evaluation argument is evaluated once applicative order. • Can we keep track of values once we’ve obtained them, and avoid cost of reevaluation? (define (normal-order?) (let ((x #t)) ((lambda (x) ’()) (begin (set! x #f) ’())) x)) thunk exp env

evaluated- result thunk 6.001 SICP 6.001 SICP 9 10

Thunks – Memoizing Implementation Laziness and Language Design

(define (evaluated-thunk? obj) • We have a dilemma with lazy evaluation (tagged-list? obj 'evaluated-thunk)) • Advantage: only do work when value actually needed (define (thunk-value evaluated-thunk) • Disadvantages (cadr evaluated-thunk)) – not sure when expression will be evaluated; can be very big issue in a language with side effects (define (force-it obj) (cond ((thunk? obj) – may evaluate same expression more than once (let ((result (actual-value (thunk-exp obj) (thunk-env obj)))) • Memoization doesn't fully resolve our dilemma (set-car! obj 'evaluated-thunk) (set-car! (cdr obj) result) • Advantage: Evaluate expression at most once (set-cdr! (cdr obj) '()) • Disadvantage: What if we want evaluation on each use? result)) ((evaluated-thunk? obj) (thunk-value obj)) (else obj))) • Alternative approach: give programmer control!

6.001 SICP 6.001 SICP 11 12

2 Variable Declarations: lazy and lazy-memo Syntax Extensions – Parameter Declarations

• Handle lazy and lazy-memo extensions in an upward- (define (first-variable var-decls) (car var-decls)) compatible fashion.; (define (rest-variables var-decls) (cdr var-decls)) (define declaration? pair?) (lambda (a (b lazy) c ( lazy-memo)) ...) (define (parameter-name var-decl) • "a", "c" are the usual kind of variables (evaluated before (if (pair? var-decl) (car var-decl) var-decl)) procedure application • "b" is lazy, “Normal Order”: it gets (re)-evaluated each (define (lazy? var-decl) time its value is actually needed (and (pair? var-decl) (eq? 'lazy (cadr var-decl)))) • "d" is lazy-memo; it gets evaluated the first time its value is needed, and then that value is returned again (define (memo? var-decl) any other time it is needed again. (and (pair? var-decl) (eq? 'lazy-memo (cadr var-decl))))

6.001 SICP 6.001 SICP 13 14

Controllably Memo-izing Thunks A new version of delay-it

• thunk – never gets memoized • Look at the variable declaration to do the right thing... • thunk-memo – first eval is remembered • evaluated-thunk – memoized-thunk that has (define (delay-it decl exp env) already been evaluated (cond ((not (declaration? decl)) (l-eval exp env)) ((lazy? decl) (list 'thunk exp env)) ((memo? decl) when thunk- exp env memo (list 'thunk-memo exp env)) forced (else (error "unknown declaration:" decl))))

evaluated- result thunk 6.001 SICP 6.001 SICP 15 16

Change to force-it Order Comparison

(define (force-it obj) (define (foo x) (cond ((thunk? obj) ;eval, but don't remember it (write-line "inside foo") (actual-value (thunk-exp obj) (+ x x)) (thunk-env obj))) ((memoized-thunk? obj) ;eval and remember (foo (begin (write-line "eval arg") 222)) (let ((result (actual-value (thunk-exp obj) (thunk-env obj)))) Applicative Normal (lazy) Memoized Normal (set-car! obj 'evaluated-thunk) Order: Order: (Lazy-memo) Order: (set-car! (cdr obj) result) eval arg inside foo inside foo (set-cdr! (cdr obj) '()) eval arg eval arg result)) inside foo eval arg ((evaluated-thunk? obj) (thunk-value obj)) => 444 => 444 (else obj))) => 444

6.001 SICP 6.001 SICP 17 18

3 Exercise Stream Object

Given this definition of l-abs: • A pair-like object, except the cdr part is lazy (not evaluated until needed): (define (l-abs (i lazy)) (if (< i 0) (- i) i)) cons-stream

Trace the following use of l-abs: stream-car stream-cdr

(define (down) (begin (set! x (- x 2)) x) a a (define x 3) value thunk-memo (l-abs (down)) (define (cons-stream x (y lazy-memo)) (cons x y)) (define stream-car car) (define stream-cdr cdr)

6.001 SICP 6.001 SICP 19 20

What will these print? (ex1) Decoupling computation from description

(define s (cons 5 (begin (write-line 7) 9))) • Can separate order of events in computer from apparent order of events in procedure description (car s) (list-ref (cdr s) (filter (lambda (x) (prime? x)) (enumerate-interval 1 100000000)) (define t (cons-stream 5 (begin (write-line 7) 9))) 100) (stream-ref (stream-car t) (stream-filter (lambda (x) (prime? x)) (stream-interval 1 100000000)) (stream-cdr t) 100)

6.001 SICP 6.001 SICP 21 22

Decoupling computation from description Decoupling computation from description

(define (stream-interval a b) (define seq (stream-interval 1 10)) (if (> a b) (define y (stream-filter even? seq)) the-empty-stream (cons-stream a (stream-interval (+ a 1) b)))) Now! Lets do some calculation...

(define (filter-stream pred str) (if (pred (stream-car str)) (stream-ref y 3) -> 8 (cons-stream (stream-car str) (filter-stream pred (stream-cdr str))) (filter-stream pred (stream-cdr str))))

6.001 SICP 6.001 SICP 23 24

4 Creating streams Creating infinite streams

There are two basic ways to create streams: (define ones (cons-streams 1 ones))

1. Build them up from scratch (define (add-streams s1 s2) (cons-stream (+ (stream-car s1) (stream-car s2)) 2. Modify or combine existing streams (add-streams (stream-cdr s1) (stream-cdr s2)))))

(define integers (cons-stream 1 (add-streams ones integers))

6.001 SICP 6.001 SICP 25 26

Creating fibonacci streams (ex4) Creating fibonacci streams

Write a procedure, (fibs), that returns a stream of Fibonacci Write a procedure, (fibs), that returns a stream of Fibonacci numbers. The Fibonacci series goes numbers. The Fibonacci series goes

(1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 (1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 ...) 6765 ...)

It starts with two 1's, then each successive value is the sum of the It starts with two 1's, then each successive value is the sum of the previous two. previous two.

(define fibs (cons-stream 0 (cons-stream 1 (add-stream (stream-cdr fibs) fibs)))) 6.001 SICP 6.001 SICP 27 28

Add-Streams == Map2

(add-streams integers integers) == (map2-stream + integers integers)

(define (map2-stream proc str1 str2) (cons-stream (proc (stream-car str1) (stream-car str2)) (map-stream2 proc (stream-cdr str1) (stream-cdr str2))))

6.001 SICP 29

5