0% found this document useful (0 votes)
34 views

PLDI Week 08 Lambda

This document discusses first-class functions in functional programming languages. It covers lambda calculus, free variables, substitution, alpha equivalence, and an operational semantics for evaluating lambda calculus expressions with examples.

Uploaded by

Victor Zhao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
34 views

PLDI Week 08 Lambda

This document discusses first-class functions in functional programming languages. It covers lambda calculus, free variables, substitution, alpha equivalence, and an operational semantics for evaluating lambda calculus expressions with examples.

Uploaded by

Victor Zhao
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

CS4212: Compiler Design

Week 8: First-Class Functions

Ilya Sergey
[email protected]

ilyasergey.net/CS4212/
First-Class Functions

Untyped lambda calculus


Substitution
Evaluation
“Functional” languages

• Languages like OCaml, Scala, Haskell, Scheme, Python, C#, Java 8, Swift
• Functions can be passed as arguments (e.g. map or fold)
• Functions can be returned as values (e.g. compose)
• Functions nest: inner function can refer to variables bound in the outer function

let add = fun x -> fun y -> x + y


let inc = add 1
let dec = add -1

let compose = fun f -> fun g -> fun x -> f (g x)


let id = compose inc dec

• How do we implement such functions?


– in an interpreter? in a compiled language?
(Untyped) Lambda Calculus
• The lambda calculus is a minimal programming language.
– Note: we’re writing (fun x -> e) lambda-calculus notation: λ x. e
• It has variables, functions, and function application.
– That’s it!
– It’s Turing Complete.
– It’s the foundation for a lot of research in programming languages.
– Basis for “functional” languages like Scala, OCaml, Haskell, etc.
Abstract syntax in OCaml:
type exp =
| Var of var (* variables *)
| Fun of var * exp (* functions: fun x → e *)
| App of exp * exp (* function application *)

Concrete syntax: exp ::=


|x variables
| fun x → exp functions
| exp1 exp2 function application
| ( exp ) parentheses
Free Variables and Scoping
let add = fun x → fun y → x + y
let inc = add 1

• The result of add 1 is a function


• After calling add, we can’t throw away its argument (or its local variables) because
those are needed in the function returned by add.
• We say that the variable x is free in fun y → x + y
– Free variables are defined in an outer scope

• We say that the variable y is bound by “fun y” and its scope is the body “x + y”
in the expression fun y → x + y

• A term with no free variables is called closed.

• A term with one or more free variables is called open.


Values and Substitution
• The only values of the lambda calculus are (closed) functions:
val ::=
| fun x → exp functions are values

• To substitute a (closed) value v for some variable x in an expression e


– Replace all free occurrences of x in e by v.
– In OCaml: written subst v x e
– In Math: written e{v/x}

• Function application is interpreted by substitution:


(fun x → fun y → x + y) 1
= subst 1 x (fun y → x + y)
Note: for the sake of
= (fun y → 1 + y) examples we may
add integers and
arithmetic operations to
the “pure” untyped
lambda calculus.
Operational Semantics of Lambda Calculus
• Substitution function (in Math):

x{v/x} = v (replace the free x by v)


y{v/x} = y (assuming y ≠ x)
(fun x → exp){v/x} = (fun x → exp) (x is bound in exp)
(fun y → exp){v/x} = (fun y → exp{v/x}) (assuming y ≠ x)
(e1 e2){v/x} = (e1{v/x} e2{v/x}) (substitute everywhere)
Operational Semantics of Lambda Calculus
• Substitution function (in Math):

x{v/x} = v (replace the free x by v)


y{v/x} = y (assuming y ≠ x)
(fun x → exp){v/x} = (fun x → exp) (x is bound in exp)
(fun y → exp){v/x} = (fun y → exp{v/x}) (assuming y ≠ x)
(e1 e2){v/x} = (e1{v/x} e2{v/x}) (substitute everywhere)

• Examples:

(x y) {(fun z → z z)/y}
= x (fun z → z z)

(fun x → x y){(fun z → z z)/y}


= fun x → x (fun z → z z)

(fun x → x){(fun z → z z)/x}


= fun x → x // x is not free!
Demo: Programming in Lambda Calculus

• https://github.com/cs4212/week-08-lambda-2023

• lambda.ml – untyped lambda-calculus


• lambda_int.ml – untyped lambda-calculus with integers
• stlc.ml – simply-typed lambda-calculus

9
Free Variable Calculation
• An OCaml function to calculate the set of free variables in a lambda expression:

let rec free_vars (e:exp) : VarSet.t =


begin match e with
| Var x -> VarSet.singleton x
| Fun(x, body) -> VarSet.remove x (free_vars body)
| App(e1, e2) -> VarSet.union (free_vars e1) (free_vars e2)
end

• A lambda expression e is closed if free_vars e returns VarSet.empty


• In mathematical notation:

fv(x) = {x}
fv(fun x → exp) = fv(exp) \ {x} (‘x’ is a bound in exp)
fv(exp1 exp2) = fv(exp1) ∪ fv(exp2)
Variable Capture
• Note that if we try to naively "substitute" an open term, a bound variable might
capture the free variables:
Note: x is free
in (fun z → x)
(fun x → (x y)){(fun z → x)/y}
= fun x → (x (fun z -> x))

free x is
“captured“!!

• Usually not the desired behaviour


– This property is sometimes called "dynamic scoping"
The meaning of "x" is determined by where it is bound dynamically,
not where it is bound statically.
– Some languages (e.g. emacs lisp) are implemented with this as a "feature"
– But: it leads to hard-to-debug scoping issues
Alpha Equivalence
• Note that the names of bound variables don't matter to the semantics
– i.e. it doesn't matter which variable names you use, as long as you use them consistently:
(fun x → y x) is the "same" as (fun z → y z)
the choice of "x" or "z" is arbitrary, so long as we consistently rename them

Two terms that differ only by consistent renaming of


bound variables are called alpha equivalent

• The names of free variables do matter:


(fun x → y x) is not the "same" as (fun x → z x)

Intuitively: y an z can refer to different things from some outer scope

Students who cheat by “renaming variables” are


trying to exploit alpha equivalence…
Fixing Substitution
• Consider the substitution operation:

e1{e2/x}

• To avoid capture, we define substitution to pick an alpha equivalent version


of e1 such that the bound names of e1 don't mention the free names of e2.
– Then do the "naïve" substitution.

For example: (fun x → (x y)){(fun z → x)/y}


= (fun x’ → (x' (fun z → x)) rename x to x'
This is fine:
(fun x → (x y)){(fun x → x)/y}
= (fun x → (x (fun x → x))
= (fun a → (a (fun b → b))
Operational Semantics
• Specified using just two inference rules with judgments of the form exp ⇓ val
– Read this notation a as “program exp evaluates to value val”
– This is call-by-value semantics: function arguments are evaluated before substitution

v⇓v

“Values evaluate to themselves”

exp1 ⇓ (fun x → exp3) exp2 ⇓ v exp3{v/x} ⇓ w

exp1 exp2 ⇓ w
“To evaluate function application: Evaluate the function to a value, evaluate the
argument to a value, and then substitute the argument for the function. ”
Demo: Implementing the Interpreter

• https://github.com/cs4212/week-08-lambda-2023

• lambda.ml – untyped lambda-calculus


• lambda_int.ml – untyped lambda-calculus with integers
• stlc.ml – simply-typed lambda-calculus

15
Adding Integers to Lambda Calculus
exp ::=
|…
|n constant integers
| exp1 + exp2 binary arithmetic operation

val ::=
| fun x → exp functions are values
|n integers are values

n{v/x} = n constants have no free vars.


(e1 + e2){v/x} = (e1{v/x} + e2{v/x}) substitute everywhere

exp1 ⇓ n1 exp2 ⇓ n2

exp1 + exp2 ⇓ (n1 ⟦+⟧ n2)

Object-level ‘+’ Meta-level ‘+’


Next Week

Semantic Analysis via Types

17

You might also like