Imperative versus Functional Languages
Two Paradigms
Programming languages can be broadly classified by the style of computation they encourage. The two paradigms most relevant to the Part IA course are the imperative and functional paradigms.
Imperative Programming
In an imperative language, a program is a sequence of statements that mutate the program’s state. Execution order matters because each statement may depend on the state left behind by the previous one.
Key characteristics:
- Mutable state: variables hold values that change over time.
x = x + 1is a common expression becausexreally does take on a new value. - Explicit control flow: loops (
for,while), conditionals (if/else), and subroutine calls drive computation - Side effects: functions can modify global state, perform I/O, or alter their arguments — the function’s effect is not limited to its return value
- Statement-oriented: computation is structured as “do this, then do that”
Examples of imperative languages: C, Java, Python, JavaScript (though all have absorbed some functional features).
Java as an Imperative Language
Java is fundamentally imperative and object-oriented. Consider:
int total = 0;
for (int i = 0; i < numbers.length; i++) {
total = total + numbers[i];
}
The variable total is explicitly mutated through each iteration. The loop counter i changes with every step. The state of the program at any point is the collection of all variable values at that point.
Functional Programming
In a functional language, a program is a collection of expressions built from pure functions. Computation proceeds by evaluating expressions rather than executing statements.
Key characteristics:
- Immutability: once a value is bound to a name, it does not change. There is no assignment statement that alters an existing variable.
- Pure functions: a function’s output depends only on its inputs. Calling the same function with the same arguments always produces the same result. There are no side effects.
- Recursion over loops: instead of mutating a loop counter, functional languages use recursive function calls to process sequences
- Expression-oriented: everything evaluates to a value. An
ifexpression returns a value; a function body is a single expression.
Examples of functional languages: OCaml, Haskell, F#, Elm.
OCaml in the Part IA Course
OCaml is taught in Foundations of Computer Science (FoCS). It is a statically typed functional language with powerful type inference and pattern matching. A typical OCaml function:
let rec sum lst =
match lst with
| [] -> 0
| x :: xs -> x + sum xs
There is no mutable variable being updated. The computation is expressed as a recursive decomposition of the list. The function is pure — its result depends only on its input list.
Where Java Stands
Java is fundamentally an imperative, object-oriented language, but it has absorbed functional features since Java 8:
- Lambda expressions: anonymous functions that can be passed as arguments
- Streams API: a functional-style pipeline for processing collections without explicit loops
- Method references: shorthand for lambdas that call a single method
- Optional: a container type that avoids null and encourages functional-style chaining
The imperative loop above can be rewritten in a functional style:
int total = Arrays.stream(numbers).sum();
No explicit mutation of a running total. No loop counter. The stream pipeline abstracts away the iteration. However, Java lambdas are still ultimately compiled into objects and invoked through interfaces — the functional style is syntactic sugar over an imperative core.
Trade-offs
Imperative
| Strengths | Weaknesses |
|---|---|
| Fine-grained control over state and memory | Unexpected mutation is a major source of bugs |
| Natural fit for inherently stateful problems (GUIs, games, network protocols) | Reasoning about program behaviour requires mentally tracking all state changes |
| Often maps directly to hardware execution model | Concurrency is hard — shared mutable state must be protected with locks |
Functional
| Strengths | Weaknesses |
|---|---|
| Easier to reason about — a function’s behaviour depends only on its inputs | Awkward for inherently stateful problems |
| Referential transparency enables safe substitution and equational reasoning | Can be less intuitive for programmers trained in imperative style |
| Trivially parallelisable — pure functions have no shared state to conflict | Performance can suffer from creating many intermediate immutable structures |
Complementarity
The two courses — OOP (Java) and FoCS (OCaml) — are designed to give you fluency in both paradigms. Each illuminates the other: understanding immutability and pure functions from OCaml helps you write better Java code (fewer mutable fields, prefer immutable objects where possible), and understanding object modelling from Java helps you appreciate OCaml’s module system as an alternative approach to abstraction.