0% found this document useful (0 votes)
10 views54 pages

Xavier Leroy - Semantics

Xavier Leroy - Semantics

Uploaded by

Sam Uel
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)
10 views54 pages

Xavier Leroy - Semantics

Xavier Leroy - Semantics

Uploaded by

Sam Uel
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/ 54

Functional programming languages

Part I: interpreters and operational semantics

Xavier Leroy

INRIA Paris-Rocquencourt

MPRI 2-4, 2016–2017

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 1 / 52


What is a functional programming language?

Various answers:
By examples: Caml, Haskell, Scheme, SML, . . .
“A language that takes functions seriously”.
A language that manipulates functions with free variables as
first-class values, following (more or less) the model of the λ-calculus.

Example 1
let rec map f lst =
match lst with [] -> [] | hd :: tl -> f hd :: map f tl

let add_to_list x lst =


map (fun y -> x + y) lst

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 2 / 52


The λ-calculus

The formal model that underlies all functional programming languages.

λ-calculus, recap
Terms: a, b ::= x | λx.a | a b

Rewriting rule: (λx.a) b → a{x ← b} (β-reduction)

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 3 / 52


From λ-calculus to a functional programming language
Take the λ-calculus and:
Fix a reduction strategy.
β-reductions in the λ-calculus can occur anywhere and in any order. This can
affect termination and algorithmic efficiency of programs. A fixed reduction
strategy enables the programmer to reason about termination and algorithmic
complexity.
Add primitive data types (integers, strings), primitive operations
(arithmetic, logical), and data structures (pairs, lists, trees, . . . ).
All these can be encoded in the λ-calculus, but the encodings are unnatural and
inefficient. These notions are so familiar to programmer as to deserve language
support.
Develop efficient execution models.
Repeated rewriting by the β rule is a terribly inefficient way to execute programs
on a computer.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 4 / 52


From λ-calculus to a functional programming language
Take the λ-calculus and:
Fix a reduction strategy.
β-reductions in the λ-calculus can occur anywhere and in any order. This can
affect termination and algorithmic efficiency of programs. A fixed reduction
strategy enables the programmer to reason about termination and algorithmic
complexity.
Add primitive data types (integers, strings), primitive operations
(arithmetic, logical), and data structures (pairs, lists, trees, . . . ).
All these can be encoded in the λ-calculus, but the encodings are unnatural and
inefficient. These notions are so familiar to programmer as to deserve language
support.
Develop efficient execution models.
Repeated rewriting by the β rule is a terribly inefficient way to execute programs
on a computer.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 4 / 52


From λ-calculus to a functional programming language
Take the λ-calculus and:
Fix a reduction strategy.
β-reductions in the λ-calculus can occur anywhere and in any order. This can
affect termination and algorithmic efficiency of programs. A fixed reduction
strategy enables the programmer to reason about termination and algorithmic
complexity.
Add primitive data types (integers, strings), primitive operations
(arithmetic, logical), and data structures (pairs, lists, trees, . . . ).
All these can be encoded in the λ-calculus, but the encodings are unnatural and
inefficient. These notions are so familiar to programmer as to deserve language
support.
Develop efficient execution models.
Repeated rewriting by the β rule is a terribly inefficient way to execute programs
on a computer.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 4 / 52


Outline

In this lecture:

1 Reduction strategies

2 Enriching the language

3 Efficient execution models


A naive interpreter
Natural semantics
Environments and closures
Explicit substitutions

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 5 / 52


Reduction strategies

Call-by-value in structural operational style (SOS)


(G. Plotkin, 1981)

Terms (programs) and values (results of evaluation):


Terms: a, b ::= N integer constant
|x variable
| λx. a function abstraction
|ab function application
Values: v ::= N | λx. a

One-step reduction relation a → a0 , in SOS:

(λx.a) v → a[x ← v ] (βv )


a → a0 b → b0
(app-l) (app-r)
ab→ a0 b v b→v b0

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 6 / 52


Reduction strategies

Example of reduction

(λx.x) 1 → x[x ← 1] = 1
(app-r)
(λx.λy . y x) ((λx.x) 1) → (λx.λy . y x) 1
(app-l)
(λx.λy . y x) ((λx.x) 1) (λx.x) → (λx.λy . y x) 1 (λx.x)

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 7 / 52


Reduction strategies

Features of the reduction relation

Weak reduction:
We cannot reduce under a λ-abstraction.
a → a0
λx.a → λx.a0

Call-by-value:
In an application (λx.a) b, the argument b must be fully reduced to a
value before β-reduction can take place.

(λx.a) v → a[x ← v ] (λx.a) (b c) → a[x ← b c]

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 8 / 52


Reduction strategies

Features of the reduction relation

Left-to-right:
In an application a b, we must reduce a to a value first before we can
start reducing b.
b → b0
v b → v b0
(Right-to-left is equally acceptable and equally easy to specify.)
Deterministic:
For every term a, there is at most one a0 such that a → a0 .
(Since values cannot reduce, there is at most one rule among (βv ),
(app-l) and (app-r) that applies to a given term.)

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 9 / 52


Reduction strategies

Reduction sequences

Elementary reductions can be chained to describe how a term evaluates:


Termination: a → a1 → a2 → . . . → v
The value v is the result of evaluating a.
Divergence: a → a1 → a2 → . . . → an → . . .
The sequence of reductions is infinite.
Error: a → a1 → a2 → . . . → an 6→
when an is not a value but does not reduce.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 10 / 52


Reduction strategies

Examples of reduction sequences

Terminating:

(λx.λy . y x) ((λx.x) 1) (λx.x) → (λx.λy . y x) 1 (λx.x)


→ (λy . y 1) (λx.x)
→ (λx.x) 1
→ 1

Error: (λx. x x) 2 → 2 2 6→

Divergence: (λx. x x) (λx. x x) → (λx. x x) (λx. x x) → . . .

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 11 / 52


Reduction strategies

An alternative to SOS: reduction contexts


(Felleisen and Hieb, 1989; Wright and Felleisen, 1992)

First, define head reductions (at the top of a term):


ε
(λx.a) v → a[x ← v ] (βv )

Then, define reduction as head reduction within a reduction context:


ε
a → a0
(context)
E [a] → E [a0 ]

where reduction context E (terms with a hole denoted [ ]) are defined by


the following grammar:
E ::= [ ] reduction at top of term
|E b reduction in the left part of an application
|v E reduction in the right part of an application

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 12 / 52


Reduction strategies

Example of reductions with contexts

In red, the subterm that head-reduces, for each reduction step.

(λx.λy . y x) ((λx.x) 1) (λx.x) take E = (λx.λy . y x) [ ] (λx.x)


→ (λx.λy . y x) 1 (λx.x) take E = [ ] (λx.x)
→ (λy . y 1) (λx.x) take E = [ ]
→ (λx.x) 1 take E = [ ]
→1

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 13 / 52


Reduction strategies

Equivalence between SOS and contexts

Reduction contexts in the Felleisen approach are in one-to-one


correspondence with derivations in the SOS approach.
For example, the SOS derivation:

(λx.x) 1 → 1
(app-r)
(λx.λy . y x) ((λx.x) 1) → (λx.λy . y x) 1
(app-l)
(λx.λy . y x) ((λx.x) 1) (λx.x) → (λx.λy . y x) 1 (λx.x)

corresponds to the context E = ((λx.λy . y x) [ ]) (λx.x)


ε
and the head reduction (λx.x) 1 → 1.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 14 / 52


Reduction strategies

Determinism of reduction under contexts

In the Felleisen approach, the determinism of the reduction relation is a


consequence of the following unique decomposition theorem:

Theorem 2
For all terms a, there exists at most one reduction context E and one term
b such that a = E [b] and b can reduce by head reduction.

(Exercise.)

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 15 / 52


Reduction strategies

Call-by-name
Unlike call-by-value, call-by-name does not evaluate arguments before
performing β-reduction. Instead, it performs β-reduction as soon as
possible; the argument will be reduced later when its value is needed in the
function body.

In SOS style:

a → a0
(λx.a) b → a[x ← b] (βn ) (app-l)
a b → a0 b
In Felleisen style:
ε
(λx.a) b → a[x ← b] with reduction contexts E ::= [ ] | E b
or in other words, E ::= [ ] b1 . . . bn

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 16 / 52


Reduction strategies

Example of reduction sequence

(λx.λy . y x) ((λx.x) 1) (λx.x) → (λy . y ((λx.x) 1)) (λx.x)


→ (λx.x) ((λx.x) 1)
→ (λx.x) 1
→ 1

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 17 / 52


Reduction strategies

Call-by-value vs. call-by-name

If M terminates in CBV, it also terminates in CBN.

Some terms terminate in CBN but not CBV:

(λx.1) ω → 1 in CBN, diverges in CBV

(with ω = (λx.x x) (λx.x x) a diverging term).

Some terms evaluate more efficiently in CBV:


(λx. x + x) M evaluates M once in CBV, twice in CBN.

Some terms evaluate more efficiently in CBN:


(λx. 1) M evaluates M once in CBV, not at all in CBN.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 18 / 52


Reduction strategies

Encoding CBN in a CBV language

Use thunks: functions λz.a (where z ∈


/ FV (a)) whose sole purpose is to
delay evaluation of a.

[[x]] = x () (where () is a constant)


[[λx.a]] = λx.[[a]]
[[a b]] = [[a]] (λz.[[b]]) (z ∈
/ FV (b))

Can be applied selectively to some but not all function parameters.

Effects on types: if a : τ then [[a]] : [[τ ]]


where [[basetype]] = basetype
and [[τ1 → τ2 ]] = (unit → [[τ1 ]]) → [[τ2 ]]

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 19 / 52


Reduction strategies

Encoding CBV in a CBN language

Much harder. See “Continuation-Passing Style” in part III.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 20 / 52


Enriching the language

Outline

1 Reduction strategies

2 Enriching the language

3 Efficient execution models


A naive interpreter
Natural semantics
Environments and closures
Explicit substitutions

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 21 / 52


Enriching the language

Enriching the language

Terms: a, b ::= N integer constant


|x variable
| λx. a function abstraction
|ab function application
| µf .λx. a recursive function
| a op b arithmetic operation
| C (a1 , . . . , an ) data structure construction
| match a with p1 | . . . | pn pattern-matching
Operators: op ::= + | − | . . . | < | = | > | . . .
Patterns: p ::= C (x1 , . . . , xn ) → a
Values: v ::= N | C (v1 , . . . , vn )
| λx. a | µf .λx. a

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 22 / 52


Enriching the language

Example
The Caml expression

fun x lst ->


let rec map f lst =
match lst with [] -> [] | hd :: tl -> f hd :: map f tl
in
map (fun y -> x + y) lst

can be expressed as

λx. λlst.
(µmap. λf. λlst.
match lst with Nil() → Nil()
| Cons(hd, tl) → Cons(f hd, map f tl))
(λy. x + y) lst

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 23 / 52


Enriching the language

Some derived forms

let and let rec bindings:

let x = a in b 7→ (λx.b) a

let f x1 . . . xn = a in b 7→ let f = λx1 . . . λxn .a in b

let rec f x = a in b 7→ let f = µf .λx.a in b

let rec f x = a 7→ let rec f g x = (let f = f g in a) in


and g y = b let rec g y = (let f = f g in b) in
in c let f = f g in c

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 24 / 52


Enriching the language

Some derived forms

Booleans and if-then-else:


true 7 → True()
false 7 → False()
if a then b else c 7→ match a with True() → b | False() → c

Pairs and projections:

(a, b) 7→ Pair(a, b)
fst(a) 7→ match a with Pair(x, y ) → x
snd(a) 7→ match a with Pair(x, y ) → y

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 25 / 52


Enriching the language

Reduction rules

Head reductions:
ε
N 1 + N2 → N if N = N1 + N2
ε
N1 < N2 → true() if N1 < N2
ε
N1 < N2 → false() if N1 ≥ N2
ε
match C (~v ) with C (~x ) → a | p~ → a[~x ← ~v ] if |~v | = |~x |
0 ε
match C (~v ) with C (~x ) → a | p~ → match C (~v ) with p~ if C 6= C 0
ε
(µf .λx.a) v → a[f ← µf .λx.a, x ← v ]

Contexts:
E ::= . . . | E op a | v op E | C (~v , E , ~a) | match E with p~

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 26 / 52


Enriching the language

Reduction rules for derived forms


Derived forms have sensible reduction rules that can be deduced from the
rules on the previous page. For instance:

ε
let x = v in a → a[x ← v ]
ε
if true then a else b → a
ε
if false then a else b → b
ε
fst(v1 , v2 ) → v1
ε
snd(v1 , v2 ) → v2

Similarly for contexts:


E ::= . . . | let x = E in a | if E then a else b
| (E , a) | (v , E ) | fst(E ) | snd(E )

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 27 / 52


Efficient execution models

Outline

1 Reduction strategies

2 Enriching the language

3 Efficient execution models


A naive interpreter
Natural semantics
Environments and closures
Explicit substitutions

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 28 / 52


Efficient execution models

A naive interpreter that follows the reduction semantics


Terms, values, substitutions:

type term =
Const of int | Var of string | Lam of string * term | App of term * term

let isvalue = function Const _ -> true | Lam _ -> true | _ -> false

let rec subst x v = function


| Const n -> Const n
| Var y -> if x = y then v else Var y
| Lam(y, b) -> if x = y then Lam(y, b) else Lam(y, subst x v b)
| App(b, c) -> App(subst x v b, subst x v c)

Note: subst function above assumes that v is closed.


(It is always the case when weakly reducing closed source terms.)
Otherwise, name capture can occur.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 29 / 52


Efficient execution models A naive interpreter

A naive interpreter that follows the reduction semantics


One-step reduction in Plotkin’s SOS style:

let rec reduce = function


| App(Lam(x, a), v) when isvalue v -> Some(subst x v a)
| App(a, b) ->
if isvalue a then begin
match reduce b with
| None -> None | Some b’ -> Some(App(a, b’))
end else begin
match reduce a with
| None -> None | Some a’ -> Some(App(a’, b))
end
| _ -> None

Iterating reduction steps:

let rec evaluate a =


match reduce a with None -> a | Some a’ -> evaluate a’

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 30 / 52


Efficient execution models A naive interpreter

Algorithmic inefficiencies

Each reduction step needs to:


1 Find the next redex, i.e. decompose the program a into E [(λx.b) v ]
⇒ time O(height(a))
2 Perform the substitution b[x ← v ]
⇒ time O(size(b))
3 Reconstruct the term E [b[x ← v ]]
⇒ time O(height(a))
Each reduction step takes non-constant time: in the worst case, linear in
the size of the program.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 31 / 52


Efficient execution models Natural semantics

Alternative to reduction sequences

We first address inefficiencies 1 and 3: finding the next redex and


reconstructing the program after reduction.
Goal: amortize this cost over whole reduction sequences to a value.

ab −−−−−−→∗ (λx.c) b −−−−→∗ (λx.c) v 0 → c[x ← v 0 ] → v
because because
∗ ∗
a → (λx.c) b → v0

Note the structure of this sequence: first, reduce a to a function value;


then, reduce b to a value; then, do the substitution; finally, reduce the
substituted term to a value.
Idea: define a relation a b ⇒ v that follows this structure.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 32 / 52


Efficient execution models Natural semantics

Natural semantics, a.k.a. big-step semantics


(G. Kahn, 1987)

Define a relation a ⇒ v , meaning “a evaluates to value v ”, by inference


rules that follow the structure of reduction sequences.
The rules for call-by-value are:

N⇒N λx.a ⇒ λx.a


a ⇒ λx.c b ⇒ v0 c[x ← v 0 ] ⇒ v
ab⇒v

For call-by-name, replace the application rule by:

a ⇒ λx.c c[x ← b] ⇒ v
ab⇒v

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 33 / 52


Efficient execution models Natural semantics

Example of an evaluation derivation

λx.x ⇒ λx.x
1⇒1
λx.x ⇒ λx.x
1⇒1
λx.λy .y x λy .y 1 1⇒1
⇒ λx.λy .y x (λx.x) 1 ⇒ 1 ⇒ λy .y 1 1⇒1
(λx.λy .y x) ((λx.x) 1) ⇒ λy .y 1 λx.x ⇒ λx.x (λx.x) 1 ⇒ 1
(λx.λy .y x) ((λx.x) 1) (λx.x) ⇒ 1

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 34 / 52


Efficient execution models Natural semantics

Evaluation rules for language extensions

a ⇒ µf .λx.c b ⇒ v0 c[f ← µf .λx.c, x ← v 0 ] ⇒ v


ab⇒v
a ⇒ N1 b ⇒ N2 N = N1 + N2
a+b⇒N
a ⇒ C (~v ) |~v | = |~x | b[~x ← ~v ] ⇒ v
match a with C (~x ) → b | p ⇒ v
a ⇒ C 0 (~v ) C 0 6= C match a with p ⇒ v
match a with C (~x ) → b | p ⇒ v

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 35 / 52


Efficient execution models Natural semantics

An interpreter that follows the natural semantics

exception Error

let rec eval = function


| Const n -> Const n
| Var x -> raise Error
| Lam(x, a) -> Lam(x, a)
| App(a, b) ->
let va = eval a in
let vb = eval b in
match va with
| Lam(x, c) -> eval (subst x vb c)
| _ -> raise Error

Note the complete disappearance of inefficiencies 1 and 3.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 36 / 52


Efficient execution models Natural semantics

Equivalence between reduction and natural semantics


Theorem 3

If a ⇒ v , then a → v .

Proof.
By induction on the derivation of a ⇒ v and case analysis on a.
If a = n or a = λx.b, then v = a and the result is obvious.
If a = b c, applying the induction hypothesis to the premises of b c ⇒ v ,
we obtain three reduction sequences:
∗ ∗ ∗
b → λx.d c → v0 d[x ← v 0 ] → v

Combining them together, we obtain the desired reduction sequence:


∗ ∗ ∗
b c → (λx.d) c → (λx.d) v 0 → d[x ← v 0 ] → v

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 37 / 52


Efficient execution models Natural semantics

Equivalence between reduction and natural semantics

Theorem 4

If a → v , where v is a value, then a ⇒ v .

Proof.
Follows from the two properties below and an easy induction on the length

of the reduction sequence a → v .
1 v ⇒ v for all values v (trivial)
2 If a → b and b ⇒ v , then a ⇒ v (case analysis).

(Exercise: complete the proof.)

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 38 / 52


Efficient execution models Environments and closures

An alternative to textual substitution


Need: bind a variable x to a value v in a term a.

Inefficient approach: the textual substitution a[x ← v ].

Alternative: remember the binding x 7→ v in an auxiliary data structure


called an environment. When we need the value of x during evaluation,
just look it up in the environment.

The evaluation relation becomes e ` a ⇒ v


e is a partial mapping from names to values (CBV).

Additional evaluation rule for variables:

e(x) = v
e`x ⇒v

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 39 / 52


Efficient execution models Environments and closures

Lexical scoping

let x = 1 in
let f = λy.x in
let x = "foo" in
f 0

In what environment should the body of the function f evaluate when we


compute the value of f 0 ?
Dynamic scoping: in the environment current at the time we evaluate
f 0. In this environment, x is bound to "foo".
This is inconsistent with the λ-calculus model and is generally
considered as a bad idea.
Lexical scoping: in the environment current at the time the function f
was defined. In this environment, x is bound to 1.
This is what the λ-calculus prescribes.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 40 / 52


Efficient execution models Environments and closures

Function closures
(P.J. Landin, 1964)

To implement lexical scoping, function abstractions λx.a must not


evaluate to themselves, but to a function closure: a pair

(λx.a)[e]
of the function text and an environment e associating values to the free
variables of the function.

let x = 1 in x 7→ 1
let f = λy.x in x 7→ 1; f 7→ (λy.x)[x 7→ 1]
let x = "foo" in x 7→ "foo"; f 7→ (λy.x)[x 7→ 1]
f 0 evaluate x in environment x 7→ 1; y 7→ 0

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 41 / 52


Efficient execution models Environments and closures

Natural semantics with environments and closures

Values: v ::= N | (λx.a)[e]


Environments: e ::= x1 7→ v1 ; . . . ; xn 7→ vn

e(x) = v
e`N⇒N e ` λx.a ⇒ (λx.a)[e]
e`x ⇒v
e ` a ⇒ (λx.c)[e 0 ] e ` b ⇒ v0 e 0 + (x 7→ v 0 ) ` c ⇒ v
e`ab⇒v

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 42 / 52


Efficient execution models Environments and closures

From variable names to de Bruijn indices


(N. de Bruijn, 1972)

Instead of identifying variables by their names, de Bruijn’s notation


identifies them by their position relative to the λ-abstraction that binds
them.

λx. (λy. y x) x
| | |
λ. (λ. 1 2) 1

n is the variable bound by the n-th enclosing λ.

Environments become sequences of values e ::= v1 . . . vn


The n-th element is the value of variable n.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 43 / 52


Efficient execution models Environments and closures

Environments, closures and de Bruijn indices

Terms: a ::= N | n | λ.a | a1 a2


Values: v ::= N | (λ.a)[e]
Environments: e ::= v1 . . . vn

e = v1 . . . vn . . . vm
e`N⇒N e ` λ.a ⇒ (λ.a)[e]
e ` n ⇒ vn
e ` a ⇒ (λ.c)[e 0 ] e ` b ⇒ v0 v 0 .e 0 ` c ⇒ v
e`ab⇒v

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 44 / 52


Efficient execution models Environments and closures

The canonical, efficient interpreter


Natural semantics + environments + closures + de Bruijn =
an interpreter with no obvious algorithmic inefficiencies.

type term = Const of int | Var of int | Lam of term | App of term * term

type value = Vint of int | Vclos of term * value environment

let rec eval e a =


match a with
| Const n -> Vint n
| Var n -> env_lookup n e
| Lam a -> Vclos(Lam a, e)
| App(a, b) ->
match eval e a with
| Vclos(Lam c, e’) ->
let v = eval e b in eval (env_add v e’) c
| _ -> raise Error

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 45 / 52


Efficient execution models Environments and closures

The canonical, efficient interpreter

The type α environment and the operations env_lookup, env_add can


be chosen among different data structures:

Data structure Cost of lookup Cost of add


List O(n) O(1)
Array O(1) O(n)
Patricia tree O(log n) O(log n)

(Here, n is the maximal number of variables in scope at any given time


≤ the maximal nesting depth of λ’s in the program.)

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 46 / 52


Efficient execution models Explicit substitutions

Environments as parallel substitutions


To reason about environment- and closure-based semantics, it is helpful to
view environments as parallel substitutions

e = v1 . . . vn ≈ [1 ← v1 ; . . . ; n ← vn ]

and closures as ordinary terms

(λ.a)[e] ≈ the substitution e applied to λ.a

Application: proving the equivalence between the natural semantics with


and without environments.
Theorem 5
e ` a ⇒ v if and only if a[e] ⇒ v .

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 47 / 52


Efficient execution models Explicit substitutions

Explicit substitutions in reduction semantics

Going one step further, the notion of environment can be internalized


within the language, i.e. presented as explicit terms with appropriate
reduction rules. This is called the λ-calculus with explicit substitutions.
Terms: a ::= N | n | λ.a | a1 a2 | a[e]
Environments/substitutions: e ::= id | a.e | . . .

Explicit substitutions, M. Abadi, L. Cardelli, P.L. Curien, J.J. Lévy, Journal of Functional
Programming 6(2), 1996.
Confluence properties of weak and strong calculi of explicit substitutions, P.L. Curien, T.
Hardin, J.J. Lévy, Journal of the ACM 43(2), 1996.

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 48 / 52


Efficient execution models Explicit substitutions

Basic reduction rules with explicit substitutions

Looking up a variable in an environment:

n[id] → n
1[a.e] → a
(n + 1)[a.e] → n[e]

Substitution distributes over application:

(a b)[e] → a[e] b[e]

β reduction:

(λ.a)[e] b → a[b.e]

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 49 / 52


Efficient execution models Explicit substitutions

Incompleteness of the basic rules

The basic rules are insufficient to obtain a nice calculus


(e.g. having the confluence property):

((λ.a)[e] b)[e 0 ]
. &
a[b.e][e 0 ] ((λa)[e][e 0 ]) b[e 0 ]

(beta reduction not applicable)

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 50 / 52


Efficient execution models Explicit substitutions

The weak λσ calculus


Add support for composition of substitutions:
Environments/substitutions: e ::= id | a.e | e1 ◦ e2

Additional rules:

a[e1 ][e2 ] → a[e2 ◦ e1 ]


e1 ◦ (e2 ◦ e3 ) → (e1 ◦ e2 ) ◦ e3
e ◦ id → e
e2 ◦ (a.e1 ) → a[e2 ].(e2 ◦ e1 )

The resulting calculus is confluent and equivalent (in computational


power) to the weak λ-calculus (without reductions under λ).

X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 51 / 52


Efficient execution models Explicit substitutions

The full λσ calculus (for reference)


To support reductions under λ, add a new kind of substitution: ↑
(Stands for {1 ← 2, 2 ← 3, . . .}.)
de Bruijn variable 2 is represented as 1[↑], variable 3 as 1[↑][↑], etc.
Reduction rules:
(Beta) (λ.a) b → a[b.id]
(App) (a b)[e] → a[e] b[e]
(VarId) 1[id] → 1
(VarCons) 1[a.e] → a
(Clos) a[e1 ][e2 ] → a[e2 ◦ e1 ]
(Abs) (λ.a)[e] → λ.(a[1.(↑ ◦ e)])
(IdL) e ◦ id → e
(ShiftId) id ◦ ↑ → ↑
(ShiftCons) (a.e) ◦ ↑ → e
(Assoc) e1 ◦ (e2 ◦ e3 ) → (e1 ◦ e2 ) ◦ e3
(Map) e2 ◦ (a.e1 ) → a[e2 ].(e2 ◦ e1 )
X. Leroy (INRIA) Functional programming languages MPRI 2-4, 2016–2017 52 / 52

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy