02 Ocaml and Lambda Calculus
02 Ocaml and Lambda Calculus
Programming Abstractions
Dr. Ritwik Banerjee
Computer Science
Stony Brook University
Syntax and Semantics
• Before the age of electronic computers, there was research on formally defining what
“computing” should mean (1920s – 1930s)
• Over time, it was shown that many of those different formalisms were equivalent in terms of
computability, i.e., anything that could be computed using one model could also be computed with
another (and vice versa)
Two main models/formalisms emerged:
1. The Turing machine, a pushdown automata with a storage tape, created by Alan Turing. This
model is the inspiration behind imperative programming.
2. The other was Lambda calculus, developed by Alonzo Church, based on parametrized
expressions. This model is the basis of all functional programming.
• Each expression was denoted by the eponymous Greek letter 𝜆
• Support for complex (recursive) data types with automatic memory allocation and de-allocation, i.e., automatic
garbage collection
• No loops! Recursion is how repeated computations are performed
• Functions are treated as values
• A variable can be assigned to a function
• Functions can use other functions as arguments, or return a function as output value (higher order functions)
• Functions are “first-class” value, i.e., no arbitrary restrictions that separate functions from other data types
(like, say, integer or string)
• All these features arise naturally from lambda calculus
• Lambda calculus is to functional programming, what algebra is to mathematics
• “universal” means that it can be used to express any computation that can be
performed by a Turing machine
• “smallest” because it’s a language with three constructs: functions, function
applications, and names, i.e., variables
Syntax
This is the complete syntax of the untyped lambda
calculus
• And yet, it’s a language powerful enough to be able
to do everything we consider to be “computation” in
the modern world
# Python
def identity(x):
return x
# or ...
identity = lambda x : x
# or ...
def nested(x):
return lambda y : x
# Python
def identity(x):
return x
y = 5
identity(y)
# Python
def identity(x):
return x
y = 5
identity(y)
But what if the original expression was 𝜆𝑥. 𝜆𝑦. 𝑥 𝑦 𝑦 and we applied
the same replacement step? We end up with 𝜆𝑦. 𝑦 𝑦 .
We changed the name of a formal parameter, and the meaning of the
program changed. That should never happen … our replacement was wrong!
• One 𝑦 is a local name, while the other is not. And we failed to distinguish
between the two identical names in two different scopes.
and Binding • Programmer can associate a name with a potentially complex fragment
of a program.
• This name usually reflects the ‘purpose’ or ‘meaning’ of that fragment
of code, and hides low-level details and reduces the conceptual
complexity for a programmer.
• Subroutines have names. Thus, names offer control abstractions.
• Data can be bundled together with operations (e.g., a class in Java or
Python; a struct in C; etc.). Thus, names offer data abstractions.
In general, the lifetime of a binding may be different from the lifetime of the
corresponding object.
An object may be retained and remain accessible even when a given name can no
longer be used.
For example, when an object is passed to a subroutine, the lifetime of the binding
inside that subroutine may be shorter than the object’s lifetime (the object may be
retained by the code that called this subroutine).
On the other hand, if the binding’s lifetime is longer than the life of the object
(i.e., the name exists, but the object does not), it is usually a bug in the program!
This is called a dangling reference, dead reference, stale pointer, etc.
Scope We often say “scope” instead of talking about the scope of a binding
• This indicates the maximal region of a program where no bindings are changed of
destroyed
• A variable is shadowed when a variable declared within a scope has the same
name as a variable declared in an outer scope
• Equivalently, the name in an inner scope is said to mask the outer name
public class Shadow { class Shadow:
private int var = 0; def __init__(self):
private void method() { self.var = 0 # Instance variable
int var = 5; // has the same name as outer object
// field, so it shadows the above def method(self):
// field inside this method var = 5 # shadows the instance variable
System.out.println(var); // prints 5 print(var) # prints 5
System.out.println(var); // prints 0 (‘this’ moves print(self.var) # prints 0 (‘self’ accesses the
// the namespace to the # instance variable by moving to
// outer scope) # the outer scope
}
}
• Objective CAML
• Generally, a good OCaml programmer can write OCaml
code that runs as efficiently as a C code written by a good C
programmer; i.e., in terms of efficiency, OCaml is as good as
any imperative programming language
• Relatively recent language, developed in 1996
• Inspired other languages like Scala and F#
• We will use OCaml as our exemplar for functional programming
• We will not get into OCaml’s object-oriented features
• We will shift our focus to object-oriented programming
using Java and Python, during the second half of this course
overview
C: OCaml:
void log() { printf(“Hello world.\n”); } let log () = print_endline “Hello world”
+ - * +. -. not &&
*. /. = <>
/ mod ** ||
== !=
< >
<= >=
= Structural equality Compares values for content equality Python’s ==; C’s ==
<> Structural inequality Opposite of =, checks if values differ Python’s !=; C’s !=
== Physical equality Checks if two references point to the Comparing object IDs in Python, or
same object in memory using Java’s ==
!= Physical inequality Opposite of ==, checks if references Similar to not in Python for object
point to different objects identity
<, >, <=, Relational Standard numerical or lexicographical Same as Python, C, or Java
>= operators comparisons
• = vs ==
• = compares content (deep); use it to
compare values (e.g., numbers, lists,
strings)
• == compares memory address (shallow);
use it to check if two variables refer to
the exact same object
• Don’t confuse equality (=) with identity (==)
• Unlike imperative languages, OCaml
does not use = for assignment
• Assignments are handled differently in
functional programming
Use with data use = for deep use .equals() for deep
structures: equality based equality based on the
on the contents contents
• let name = expr binds the name to the expression for the
rest of the program
• let name = expr in other_expr binds name to expr
only within the scope of other_expr
• Each binding is local (just like in 𝜆 calculus), and so, the let
keyword can be used in succession
• let x = 4 in let x = ...
OCaml Concept
5 x • Both boxes are labeled “x”
x • If the question being raised is “what’s in box x?”,
10
the answer depends on where we are
• But no matter the answer, the contents of the boxes
let x = 5 in (* outer scope *) never change
(let x = 10 in (* shadows outer x *) • Values in OCaml are immutable
print_int x; (* prints 10 *)
• let creates bindings, but we are neither re-
x + 2) + x;; (* prints (10 + 2) + 5 = 17 *)
assigning the same variable, nor modifying
let x = 5;; (* global scope *) the value associated with the old variable
let x = 10;; (* shadows the previous x *)
print_int x;; (* prints 10 *)
Tuples
Lists
List functions • Apply a function f recursively to the current result together with an
element of a list, to finally produce a single element:
• List.fold_left f a [b1; b2; … bn] = f(… (f (f a
b1) b2) …) bn)
List functions • Apply a function f to each element of a list, to produce unit as the
result (used for side effects):
• List.iter f [a1; a2; …; an] = begin f a1; …; f an;
() end
Bindings in 2. The pattern _ matches a value v, and doesn’t bind to anything (this is a
wildcard, and it represents an anonymous variable)
pattern matching 3. The nil pattern [] only matches the nil value [], and does not bind
to anything
4. If a pattern p1 matches a value v1, and produces a set of bindings b1;
and a pattern p2 matches v2, and produces a set of bindings b2, then
p1::p2 matches v1::v2 and produces the set of bindings b1 ∪ b2
(similarly for tuples)
Based on these rules, we can evaluate general pattern matching expressions of the type
match e with p1 -> e1 | p2 -> e2 | … | pn -> en;;