CS 251 SpringFall 2019 2020 Principles of of Programming Programming Languages Languages λ Ben Wood

Lexical and Function Closures +closures.rkt

https://cs.wellesley.edu/~cs251/s20/ Lexical Scope and Function Closures 1 Topics

• Lexical vs dynamic scope • Closures implement lexical scope. • Design considerations: why lexical scope? • Relevant design dimensions

Lexical Scope and Function Closures 2 A question of scope (warmup)

(define x 1) (define f (lambda (y) (+ x y))) (define z (let ([x 2] [y 3]) (f (+ x y))))

What is the argument value passed to this function application?

Lexical Scope and Function Closures 3 A question of scope

(define x 1) (define f (lambda (y) (+ x y))) (define z (let ([x 2] [y 3]) (f (+ x y))))

What is the value of x when this function body is evaluated for this function application?

Lexical Scope and Function Closures 4 A question of free variables A variable, x, is free in an expression, e, if x is referenced in e outside the scope of any binding of x within e. x is a free variable (define x 1) of the lambda expression. (define f (lambda (y) (+ x y))) (define z (let ([x 2] [y 3]) (f (+ x y))))

To what bindings do free variables of a function refer when the function is applied?

Lexical Scope and Function Closures 5 Answer 1: lexical (static) scope A variable, x, is free in an expression, e, if x is referenced in e outside the scope of any binding of x within e. x is a free variable (define x 1) of the lambda expression. (define f (lambda (y) (+ x y))) (define z (let ([x 2] [y 3]) (f (+ x y))))

Free variables of a function refer to bindings in the environment where the function is defined, regardless of where it is applied. Lexical Scope and Function Closures 6 Answer 2: dynamic scope A variable, x, is free in an expression, e, if x is referenced in e outside the scope of any binding of x within e. x is a free variable (define x 1) of the lambda expression. (define f (lambda (y) (+ x y))) (define z (let ([x 2] [y 3]) (f (+ x y))))

Free variables of a function refer to bindings in the environment where the function is applied, regardless of where it is defined. Lexical Scope and Function Closures 7 Answer 2: dynamic scope A variable, x, is free in an expression, e, if x is referenced in e outside the scope of any binding of x within e. x is a free variable (define x 1) of the lambda expression. (define f (lambda (y) (+ x y))) (define z (let ([x 2] [y 3]) (f (+ x y))))

Free variables of a function refer to bindings in the environment where the function is applied, regardless of where it is defined. Lexical Scope and Function Closures 8 Closures implement lexical scope.

Closures allow functions to use any binding in the environment where the function is defined, regardless of where it is applied.

Lexical Scope and Function Closures 9 Anonymous function definition expressions

Syntax: (lambda (x1 … xn) e) Week 1 – parameters: x1 through xn are identifiers – body: e is any expression

Evaluation: 1. The result is a function closure, ⟨E, (lambda (x1 … xn) e)⟩, holding the current environment, E, and the function.

[closure] E ⊢ (lambda (x1 … xn) e) ↓ ⟨E, (lambda (x1 … xn) e)⟩

Note: – An anonymous function definition is an expression. – A function closure is a new kind of value. Closures are not expressions. – This is a definition, not a call. The body, e, is not evaluated now. – lambda from the λ-calculus. Lexical Scope and Function Closures 10 Function application (call)

Syntax: (e0 e1 ... en) Week 1

Evaluation: 1. Under the current dynamic environment, E, evaluate e0 through en to values v0, …, vn. 2. If v0 is a function closure of n arguments, ⟨E', (lambda (x1 … xn) e)⟩ then The result is the result of evaluating the closure body, e, under the closure environment, E', extended with argument bindings: x1 ⟼ v1, …, xn ⟼ vn. Otherwise, there is a type error.

Functions 11 Function application (call) Week 1 Syntax: (e0 e1 … en)

Evaluation: E ⊢ e0 ↓ ⟨E', (lambda (x1 … xn) e)⟩ E ⊢ e1 ↓ v1 … E ⊢ en ↓ vn x1 ⟼ v1, …, xn ⟼ vn, E' ⊢ e ↓ v [apply] E ⊢ (e0 e1 … en) ↓ v

Functions 12 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value def (define x 1) Current evaluation step: (definedef (f y)

(let ([x (+ y 1)]) Current environment: (lambda (z) (+ x y z)) )

(define z (let ([x 3] [g (f 4)] [y 5])

(g 6) ))

Lexical Scope and Function Closures 13 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1 def (define (f y)

(letdef ([x (+ y 1)])

(lambda (z) (+ x y z)) )

(define z (let ([x 3] [g (f 4)] [y 5])

(g 6) ))

Lexical Scope and Function Closures 14 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) )

(define z (let ([x 3] [g (f 4)] [y 5]) def (g 6) ))

Lexical Scope and Function Closures 15 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) ) let (lambda (z) (+ x y z)) )

(define z (let ([x 3] [g (f 4)] [y 5]) def let (g 6) ))

Lexical Scope and Function Closures 16 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) ) x 3 (define z (let ([x 3] let [g (f 4)] [y 5]) def let (g 6) ))

Lexical Scope and Function Closures 17 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1 app (define (f y) (lambda (y) app f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) ) app y 4 x 3 (define z (let ([x 3] let [g (f 4)] [y 5]) def let (g 6) ))

Lexical Scope and Function Closures 18 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) app (+ x y z)) ) ) (lambda (z) app (+ x y z)) ) y 4 x 3 let (define z (let ([x 3] x 5 [g (f 4)] app [y 5]) def let (g 6) ))

Lexical Scope and Function Closures 19 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) y 4 (+ x y z)) ) app x 3 let (define z (let ([x 3] x 5 [g (f 4)] app app [y 5]) def (lambda (z) env let (+ x y z)) (g 6) ))

Lexical Scope and Function Closures 20 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) ) y 4 x 3 (define z (let ([x 3] x 5 [g (f 4)] [y 5]) g def let (lambda (z) env (+ x y z)) (g 6) )) let

Lexical Scope and Function Closures 21 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) ) y 4 x 3 (define z (let ([x 3] x 5 [g (f 4)] [y 5]) g def (lambda (z) env (+ x y z)) (g 6) )) y 5 let

let Lexical Scope and Function Closures 22 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) ) y 4 app x 3 (define z (let ([x 3] x 5 [g (f 4)] [y 5]) g def (lambda (z) env (+ x y z)) (g 6) )) y 5 app let z 6 app let Lexical Scope and Function Closures 23 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) def (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) ) y 4 x 3 (define z (let ([x 3] x 5 [g (f 4)]app [y 5]) g def (lambda (z) env (+ x y z)) (g 6) )) y 5 let z 6 app app let 15 Lexical Scope and Function Closures 24 env pointer Example: shows env structure, by pointing to “rest of environment” returning a function binding maps variable name to value

(define x 1) x 1

(define (f y) (lambda (y) f env (let ([x (+ y 1)]) (lambda (z) (let ([x (+ y 1)]) (+ x y z)) ) )

(lambda (z) (+ x y z)) ) y 4 x 3 (define z (let ([x 3] x 5 [g (f 4)] g [y 5]) (lambda (z) env (+ x y z)) def (g 6) )) y 5 z 6 z 15 def Lexical Scope and Function Closures 25 Debrief

1. Closures implement lexical scope. 2. Function bodies can use bindings from the environment where they were defined, not where they were applied. 3. The environment is not a stack. – Multiple environments (branches) may be live simultaneously. – CS 240's basic stack model will not suffice. – General case: heap-allocate the environment. GC will clean up for us!

Lexical Scope and Function Closures 26 PL design quiz

Java methods and functions do not need closures because they ______. a. cannot refer to names defined outside the method/function b. are not first class values c. do not use lexical scope d. are not anonymous (i.e., they are named)

Which, if any, are correct? Why?

Lexical Scope and Function Closures 27 Why lexical scope?

Lexical scope: use environment where function is defined.

Dynamic scope: use environment where function is applied.

History has shown that lexical scope is almost always better.

Here are some precise, technical reasons (not opinion).

Lexical Scope and Function Closures 28 Why lexical scope?

1. Function meaning does not depend on name choices.

Example: change body of f to replace x with q. (!) It is important in – Lexical scope: it cannot matter both cases that no – Dynamic scope: depends how result is used other variable named q is used in f. (define (f y) (let ([x (+ y 1)]) (lambda (z) (+ x y z))))

Example: remove unused variables. – Dynamic scope: but maybe some g uses it (weird).

(define (f g) (let ([x 3]) (g 2)))

Lexical Scope and Function Closures 29 Why lexical scope?

2. Functions can be understood fully where defined. There are no "hidden parameters."

Example: – Under dynamic scope: tries to add #f, unbound variable y, and 4.

(define (f y) (let ([x (+ y 1)]) (lambda (z) (+ x y z)) (define x #f) (define g (f 7)) (define a (g 4))

Lexical Scope and Function Closures 30 Why lexical scope?

3a. Closures automatically “remember” the data they need.

(define (greater-than-x x) (lambda (y) (> y x)))

(define (no-negs xs) (filter (greater-than-x -1) xs))

(define (all-greater xs n) (filter (lambda (x) (> x n)) xs))

Lexical Scope and Function Closures 31 Why lexical scope?

3b. Closures are a useful way to avoid recomputation.

These functions filter lists of lists by length.

(define (all-shorter-than-1 lists mine) (filter (lambda (xs) (< (length xs) (length mine))) lists))

(define (all-shorter-than-2 lists mine) (let ([len (length mine)]) (filter (lambda (xs) (< (length xs) len)) lists)))

How many times is the length function called during all-shorter…?

Lexical Scope and Function Closures 32 Dynamic scope?

Lexical scope is definitely the right default for variables. – Nearly all modern languages

Early LISP used dynamic scope. – Even though inspiration (lambda calculus) has lexical scope. – Later "fixed" by Scheme (Racket's parent) and other languages. – Lisp still uses dynamic scope.

Dynamic scope is very occasionally convenient: – Racket has a special way to do it. – has something similar. – Most languages are purely lexically scoped. – Exception raise/handle, throw/catch is like dynamic scope.

Lexical Scope and Function Closures 33 Remember when things evaluate!

A function body is not evaluated until the function is called.

A function body is evaluated every time the function is called.

A function call's arguments are evaluated before the called function's body.

A binding evaluates its expression when the binding is evaluated, not every time the variable is used. As with lexical/dynamic scope, there are other options here that Racket does not use. We will consider some later.

Lexical Scope and Function Closures 34 Relevant PL design dimensions in the Racket language: – scope: lexical (static) • vs. dynamic – parameter passing: pass-by-value (call-by-value) • vs. by-reference, by-name, by-need – evaluation order: eager (strict) • vs. lazy in our definitions of the Racket language (subset): – environments and closures • vs. substitution – big-step operational semantics • vs. small-step

More on all of these dimensions (and alternatives) later!

Lexical Scope and Function Closures 35