Introduction to Functional Programming

Russ Ross

Computer Laboratory
University of Cambridge
Lent Term 2005

Texts

Main Text:

Other Useful Reading:

Notes and exercises available from:

Imperative Programming

Imperative (or procedural) programs rely on modifying a state by using a sequence of commands.

The state is mainly modified by the assignment command, written v = E or v := E.

We can execute one command before another by writing them in sequence, perhaps separated by a semicolon: C1 ; C2.

Commands can be executed conditionally using if, and repeatedly using while.

Programs are a series of instructions on how to modify the state.

Imperative languages, e.g., FORTRAN, Algol, C, Pascal, Modula-3, Java support this style of programming.

An Abstract View

We ignore input-output operations, and assume that a program runs for a limited time, producing a result.

We can consider the execution in an abstract way as:

σ0 → σ1 → σ2 → … σn

The program is started with the computer in an initial state σ0, including the inputs to the program.

The program finishes with the computer in a final state σn, containing the output(s) of the program.

The state passes through a finite sequence of changes to get from σ0 to σn; in general, each command may modify the state.

Functional Progamming

A functional program is simply an expression, and executing the program means evaluating the expression. We can relate this to the imperative view by writing σn = E0].

But on the positive side:

Functional languages support this style of programming.

Example: the factorial

The factorial function can be written imperatively in C as follows:

int fact(int n) {
    int x = 1;
    while (n > 0) {
        x = x * n;
        n = n - 1;
    }
    return x;
}

wheras it would be expressed in ML as a recursive function:

fun fact n =
  if n = 0 then 1
  else n * fact (n - 1)

Advantages

At first glance, a language without variables, assignment, and sequencing looks very impractical. By avoiding variables and assignments and adding higher-order functions, we gain the following advantages:

Disadvantages

Functional programming is not without its deficiencies. Some things are harder to fit into a purely functional model, e.g.

Functional languages correspond less closely to current hardware. Historically they have been less efficient, and it can be hard to reason about their time and space usage. Better compilers and runtime systems have largely closed the performance gap.

ML is not a purely functional language, so you can use variables and assignments if required. Most of our work in this course is in the purely functional subset.

Expressions

A functional program is just an expression to be evaluated.

An expression is built up from simpler expressions by means of function applications.

f E
E1 + E2
if B then E1 else E2

There are no explicit notions of variable assignment, sequencing or control.

Without mutation (state change) or side-effects, the same expression always evaluates to the same value, and can be replaced by that value without affecting the program. This is called referential transparency.

Reading from the keyboard can return different values each time, so a function that reads from the keyboard would violate referential transparency.

Recursion

Recursive definition of functions is crucial to functional programming. There is no other mechanism for looping

Instead of variables, functional languages have value bindings: names bound to values that cannot be changed.

Without variable updates, iterative constructs like for, foreach, while, etc., are useless—they rely on state changes and side-effects.

Likewise, sequencing expressions, e.g., E1 ; E2, is useless. The result of evaluating E1 is thrown away and E2 isn't affected by it—it's just wasted work. It's only useful if E1 has a side-effect.

Recursion generalizes loops and gives a way to bind a new value to a name for the next iteration.

Type Checking

ML provides static (compile-time) type checking, which can help catch many programming errors.

Types are inferred—the compiler infers the type of an expression automatically. Type declerations aren't required. If you do something that doesn't make sense, the compiler will complain.

Types in ML may be polymorphic.

- fun first (a, b) = a
- first (1, 2)1
- first ("free", "lunch")"free"
- first (1)Type error

Brief History

The basic history leading to ML:

Many modern non-functional languages incorporate more and more features formerly associated with functional languages.

Rest of the Course

11 more lectures covering

Running ML

ML provides an interactive session. Enter an expression, ML returns a value.

Moscow ML version 2.00 (June 2000)
Enter `quit();' to quit.
- (2*4) + 18;
> val it = 26 : int
- 2.0 * 2.0 * 3.14159;
> val it = 12.56636 : real
-

Before the next lecture, make sure you have a usable ML installation. Use the Intel lab (upstairs) or install Moscow ML on your own machine.