Functional Programming
Programming Language Paradigms
- Imperative: change state, assignments
- Structured: if/block/routine control flow
- Object-oriented: message passing (dynamic dispatch), inheritance
- Functional: functions are first-class citizens that can be passed around or called recursively.
Functional Programming
- By now, you know OOP and Structured Imperative
- Functional programming
- Computation is evaluating (math) functions
- Avoid “global state” and “mutable data”
- Get stuff done: apply (higher-order) functions
- Avoid sequential commands
- Important features
- Higher-order, first-class functions
- Closures and recursion
- Lists and list processing
State
- The state of a program is all of the current variable and heap values
- Imperative programs destructively modify existing state
- Functional programs yield new similar states over time.
Basic OCaml
Let us start with a C example:
double avg(int x, int y) { double z = (double)(x + y); z = z/2; printf("Answer is %g\n", z); return z; }
Basic OCaml
OCaml version:
let avg (x:int) (y:int) : float = begin let z = float_of_int(x + y) in let z = z /. 2.0 in printf "Answer is %g\n" z; z end
The Tuple (or Pair)
Example 1:
let x = (22, 58) in (* tuple creation *) ... let y, z = x in (* tuple field extraction *) printf "first element is %d\n" y; ...
Example 2:
let add_points p1 p2 = let x1, y1 = p1 in let x2, y2 = p2 in (x1 + x2, y1 + y2)
List Syntax in OCaml
- Empty list:
[]
- Singleton:
[ element ]
- Longer list:
[ e1; e2; e3]
- Cons:
x :: [y;z]
=[x;y;z]
- Append:
[w;x] @ [y;z]
=[w;x;y;z]
- Every element must have the same type
Functional Example
Simple functional set (built out of lists)
let rec add_elem (s, e) = if s = [] then [e] else if List.hd s = e then s else List.hd s :: add_elem(List.tl s, e)
Pattern matching version
let rec add_elem (s, e) = match s with | [] -> [e] | hd :: tl when e = hd -> s | hd :: tl -> hd :: add_elem(tl, e)
Imperative Code
C version; more cases to handle
List* add_elem(List *s, item e) { if (s == NULL) { return list(e, NULL); } else if (s->hd == e) { return s; } else if (s->tl == NULL) { s->tl = list(e, NULL); return s; } else { return add_elem(s->tl, e); } }
Functional-Style Advantages
- Tractable program semantics
- Procedures are functions
- Formulate and prove assertions about code
- More readable
- Referential transparency
- Replace any expression by its value without changing the result
- No side-effects
- fewer errors
Functional-Style Disadvantages
- Efficiency
- Copying takes time
- Compiler implementation
- Frequent memory allocation
- Unfamiliar (to you!)
- New programming style
- Not appropriate for every program
- Operating systems, etc.
ML Innovative Features
- Type system
- Strongly typed
- Type inference
- Abstraction
- Modules
- Patterns
- Polymorphism
- Higher-order functions
- Concise semantics
Type System
Type inference
let rec add_elem (s, e) = match s with | [] -> [e] | hd :: tl when e = hd -> s | hd :: tl -> hd :: add_elem(tl, e) val add_elem : 'a list * 'a -> 'a list
'a list
meansList<T>
- ML infers types
- Optional type declarations
(exp : type)
- Clarify ambiguous cases, documentation
- Can be read
exp
has typetype
Pattern Matching
Simplifies Code (elimates if, accessors)
type btree = (* binary tree of strings *) | Node of btree * string * btree | Leaf of string let rec height tree = match tree with with | Leaf _ -> 1 | Node(x, _, y) -> 1 + max (height x) (height y) let rec mem tree elt = match tree with | Leaf str -> str = elt | Node(x,str,y) -> str = elt || mem x elt || mem y elt
Pattern Matching Mistakes
What if I forget a case?
let rec is_odd x = match x with | 0 -> false | 2 -> false | x when x > 2 -> is_odd(x-2)
Message
Warning 8: this pattern-matching is not exhaustive. Here is an example of a case that is not matched: 1
Polymorphism
Functions and type type inference are polymorphic
Operate on more than one type
- Example:
let rec length x = match x with | [] -> 0 | hd :: tl -> 1 + length tl val length: 'a list -> int
- The
'a
means “any one type”
Higher-Order Functions
- Functions are first-class values
- Can be used whenever a value is expected
- Notably, can be passed around
- Closure capures the environment
let rec map f lst = match lst with | [] -> [] | hd :: tl -> f hd :: map f tl val map : ('a -> 'b) -> 'a -> 'b list
- Extremely powerful programming technique
- General iterators
- Implement abstraction
Fold
- We have seen
length
andmap
- We can also imagine …
sum [1; 5; 8]
=14
product [1; 5; 8]
=40
and [true; true; false]
=false
or [true; true; false]
=true
filter (fun x -> x > 4) [1; 5; 8]
=[5; 8]
reverse [1; 5; 8]
=[8; 5; 1]
mem 5 [1; 5; 8]
=true
Fold
The
fold
operator comes from Recursion Theory (Kleene, 1952)let rec fold f acc lst = match lst with | [] -> acc | hd :: tl -> fold f (f acc hd) tl val fold: ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
Imagine we are summing a list:
f
is addition
Fold Examples
- Let us build things with
fold
length lst = fold (fun acc elt -> acc + 1) 0 lst
sum lst = fold (fun acc elt -> acc + elt) 0 lst
product lst = fold (fun acc elt -> acc * elt) 1 lst
and lst = fold (fun acc elt -> acc & elt) true lst
- Can we implement
or
orreverse
withfold
?
More Fold Examples
- Examples
reverse lst = fold (fun acc e -> acc @ [e]) [] lst
filter keep_it lst = fold (fun acc elt -> if keep_it elt then elt :: acc else acc) [] lst
mem wanted lst = fold (fun acc elt -> acc || wanted = elt) false lst
Map From Fold
let map f lst = fold (fun acc -> elt -> (f elt) :: acc) [] lst
- Types:
f : 'a -> 'b
lst : 'a list
acc : 'b list
elt : 'a
Partial Application and Currying
Currying: “if you fix some arguments, then you get a function of the remaining arguments”
Example:
let myadd x y = x + y val myadd : int -> int -> int let addtwo = myadd 2 val addtwo : int -> int addtwo 77 = 79
Referential Transparency
- To find the meaning of a functional program, we replace each reference to a variable with its definition.
- This is called referential transparency
Example:
let y = 55 let f x = x + y f 3 --> means --> 3 + y --> means --> 3 + 55
Insertion Sort in OCaml
let rec insert_sort cmp lst =
match lst with
| [] -> []
| hd :: tl -> insert cmp hd (insert_sort cmp tl)
and insert cmp elt lst =
match lst with
| [] -> [elt]
| hd :: tl when cmp hd elt -> hd :: (insert cmp elt tl)
| _ -> elt :: lst
Sorting
sort
type:(sort : ('a * 'a -> bool) -> 'a list -> 'a list
Examples:
langs = ["fortran"; "algol"; "c"] sort (fun a b -> a < b) langs sort (fun a b -> a > b) langs sort fun a b -> strlen a < strlen b) langs
Functional Programming Summary
- In functional programming, functions are first-class citizens that operate on, and produce, immutable data.
- Functions and type inference are polymorphic and operate on more than one type.
- OCaml (and Haskell, etc.) support pattern matching over user-defined data types.
fold
is a powerful and general higher-order function.