Lecture 1: Operational semantics and reduction strategies
Teacher: François Pottier
Functional Programming (FP): Introduction
G. Béry: « Machines are wondeful tools to amplify your mistakes. »
What is FP? A certain family of programming languages. In the 80’s, they were set apart from other languages, seen as “academic”. But nowadays, the disctinction is less clear, they’re extensively used in the industry as well.
Features:
 Mutable states is discouraged (avoid changing values of variables (as in mathematics))
 Higherorder functions
 Type discipline/system to avoid doing basic mistakes that would make the program crash.
 Some unsafe operations are taken away from the programmer (ex: memory deallocation: taken care of by garbage collector)
 Close to mathematics (Haskell enthusiasts like to think of programs as being mathematical entities, rather doing things)
A C programmer would think of programs as memory blocks and pointers, whereas a functional programmer think with algebraic data structures.
Loops are replaced by tailrecursive functions. Tail calls are cheap (same as doing a GOTO).
Today: functional programming not that different from “mainstream” programming, it’s a culture in and by itself.
Operational semantics and reduction strategies
Two ways to regard programs:
 Denotational semantics: What mathematical function corresponds to a given $λ$term? Define a suitable notion of space (Scott domains, etc…)
 Operational semantics: see a program as an operational “machine”
Reduction strategies in $λ$calculus
Callbyvalue
Compute the value of the argument before calling the function.
 Values:

variables and $λ$abstraction
Callbyvalue reduction $t ⟶_{cbv} t’$ inductively defined. Smallstep operational semantics:
Besides, we only reduce closed terms.
But warning: we can’t reduce under a $λ$abstraction! (Thus, values don’t reduce)
Ex:
 Coq, Agda do reduce under $λ$’s (it’s the case when you want to do mathematical reasoning), contrary to C, Java, Python, OCamL, …
 Haskell has a lazy evaluation strategy, unlike callbyvalue
Lefttoright evaluation (ex: not like in C): in $tu$, $t$ must be reduced to a value before $u$.
If $t ⟶_{cbv} u, u’$, then $u = u’$.
Then, there are 3 mutually exclusive situations:
 either a program terminates

or it diverges
 ex: $Ω ≝ δ δ$

or it yields a runtime error: when a term can’t be reduced anymore (the term is said to “go wrong”).
 not happening in $λ$calculus because it’s too “small/simple”
 $δ 2 ⟶{cbv} 22 \not⟶{cbv}$
Strong type systems rule out errors. Some type systems rule out both errors and divergence (as Coq and Agda, as a divergent program could have any type, and thus could be of type $⊥$)
Alternative style: evaluation contexts
 Head reduction:

(λx.t)v ⟶_{cbv}^{head} t[v/x]
And then reduction as head reduction under an evaluation context:
where
Callbyname
The term $u$ represents a computation that has not yet been carried out
NB: we don’t have $\cfrac{t ⟶{cbv} t’}{tu ⟶{cbv} t’u}$ has it would defeat the very purpose of callbyname (the argument of a function may be evaluated right away), and it would make things nondeterministic.
Reduced later whenever the function demands its value.
Th: if $t$ terminates under CBV, then it does under CBN too.
The converse is false: $(λx.1)Ω$ converges under CBN but diverges under CBV.
Not evaluating the argument
 may be a good thing, for ex if the function doesn’t use it
 but may backfire if it used several times in the function (you would need to use memoization, not compute the value several times)
Thunks
To embed CBN inside a CBV language.
 Thunks:

function $λ_.u$ that delays the evaluation of the argument $u$
 $⟦\bullet⟧$ is correct if:

t ⟶^\ast_{cbn} v ⟹ ⟦t⟧ ⟶^\ast_{cbn} ⟦v⟧
More visually:
 Typing judgment:

\underbrace{Γ}_{\text{assumption}} ⊢ \underbrace{t}_{\text{term}} : \underbrace{T}_{\text{type}}
This transformation $⟦\bullet⟧$ is typepreserving:
Type transformation:
and
For a closed program of type int
: $t: int ⟹ ⟦t⟧: int$
Callbyneed/Lazy evaluation (LE)
Introduces memoization to callbyname to avoid repeated computations.
Ex: it is used in Haskell
This encourages a modular way of writing programs.
With LE, you can write programs that produce large chunks of data, and a consumer that consumes just a part of this data, and when combining it, the computations carried out are the ones needed by the consumer.
Ex: Square root computation in Haskell with NewtonRaphson iterations:
next n x = (x+n/x)/2
repeat f a = a : (repeat f (f a))  producer of infinite stream of numbers
within eps (a : b : rest) =  consumer: decides how many elements to demand
if abs (ab) <= eps then b
else within eps (b : rest)  b is evaluated only only thanks to memoization
sqrt a0 eps n =
within eps (repeat (next n) a0)
No mutable state.
Other example: if you want to define a function that determines if an list element satisfies a certain property:
any: (a > Bool) > [a] > Bool
any p xs = fold (or) (map p xs) false
this would be inefficient in callbyvalue (we would apply p
to all the list elements), whereas it’s okay in callbyneed: whenever an element satisfies p
, the computation stops.
Memoization thunks
Can be implemented in OCamL: exercise.
Machinechecked proofs
To scale up to check proofs for realworld languages. Moreover, proofs in LaTeX are not maintainable and prone to mistakes.
Machanized theorem proving:
 the 4color theorem (Gonthier, Werner)
 FeitThompson theorem (Gonthier et al.)
 Kepler’s conjecture (Hales et al.)
Proof assistants are perfectly suited to programming languages:
 discrete objects (no reals, analysis, topology)
 many similar proof cases
 syntactic techniques
Today, at POPL: 20% of the papers come with machinechecked proof.
The proof checker should be as tiny and reliable as possible.
Coq
Pure functional programming language in the style of ML, with recursive functions and patternmatching:
Fixpoint factorial (n : nat) :=
match n with
 0 => 1
 S p => n * factorial p
end.
Inductive types:
Inductive nat:Type :=
 0: nat
 S: nat > nat.
Inductive predicates: generated by repeated application of the constructor
Inductive even nat > Prop :=
 even_zero : even 0
 enven_plus_2 : forall n, even n > even (S (S n)).
NB: Inhabitants of even n
can be thought of as derivation trees whose conclusion is even n
.
Abstract syntax with binders
In most programming languages: there are constructs that bind variables:
 in terms:
 function abstraction: $λx.t$
 local definitions:
let x=t in x
 in types:
 quantifiers: $∀α. α → α$
$α$equivalence $≡_α$
In papers, it’s common, by abuse of notation, to confuse $≡_α$ with equality $=$
Problem: Coq doesn’t support quotient types. There is “hack” to simulate them (Cohen, 2013), but it’s not very convenient.
Possible workaround: De Bruijn indices (but it’s a mixed blessing: simpler to formalize, but less humanreadable):
Inductive term :=
 Var: nat > term
 Lam: term > term
 App: term > term > term.
Substitutions
 Substitution $σ$:

a total function from variables to terms.
Substitutions can also be seen as infinite sequences
 Application of $σ$ to a term $t$:

$t[σ]$, defined inductively as:
x[σ] = σ(x)\\ (λt)[σ] = λ(t[0 \cdot (σ; +1)])\\ (t_1 t_2)[σ] = t_1[σ] t_2[σ]And the composition should be mutually defined as:
(σ_1; σ_2)(x) = (σ_1(x))[σ_2]
NB: cannot be written like that in Coq (not wellfounded!) ⟶ solution: define $0 \cdot (σ; +1)$ in another way (independently).
 Completeness:

if you can write an equation with the given grammar, then it is either a consequence of a theory or the negation thereof is.
Explicit Substitutions
And, define syntax for substitutions
Leave a comment