📄 Paper 2 · 4.12 Functional Programming
4.12.1b Functional Programming — Closures, Currying & Recursion
AQA 7517 · A-Level Computer Science · ~15 min read

Closures

A closure is a function that captures variables from the scope in which it was defined — even after that scope has finished executing. The function "closes over" the free variables.

-- makeAdder returns a function that adds n to its argument makeAdder n = \x -> n + x add5 = makeAdder 5 -- add5 is a closure over n=5 add5 3 -- returns 8 add5 10 -- returns 15

Here, add5 remembers the value n=5 even though makeAdder has finished. This is a closure.

Currying and Partial Application

Currying is the technique of transforming a function that takes multiple arguments into a chain of functions each taking a single argument.

-- Uncurried: takes both args at once add (x, y) = x + y -- Curried: takes one arg, returns function expecting another addC x y = x + y addC 3 -- returns function \y -> 3 + y addC 3 4 -- returns 7

Partial application: supplying fewer arguments than a function expects, producing a new function waiting for the remaining arguments. In Haskell, all multi-argument functions are curried by default.

-- Partial application example multiply x y = x * y triple = multiply 3 -- partially applied: triple y = 3 * y triple 5 -- returns 15 triple 9 -- returns 27

Recursion in Functional Programming

Because functional programming avoids mutable state, loops are replaced with recursion. The function calls itself with a smaller input until reaching a base case.

-- Recursive factorial factorial 0 = 1 -- base case factorial n = n * factorial (n - 1) -- recursive case factorial 4 -- = 4 * factorial 3 -- = 4 * 3 * factorial 2 -- = 4 * 3 * 2 * factorial 1 -- = 4 * 3 * 2 * 1 * factorial 0 -- = 4 * 3 * 2 * 1 * 1 = 24

Tail Recursion

A recursive call is tail recursive if the recursive call is the very last operation — no work is done after the call returns. Compilers can optimise tail recursion into a loop (tail call optimisation, TCO), preventing stack overflow for large inputs.

-- Tail-recursive factorial using accumulator factHelper acc 0 = acc factHelper acc n = factHelper (acc * n) (n - 1) factorial n = factHelper 1 n -- factHelper 1 4 → factHelper 4 3 → factHelper 12 2 → factHelper 24 1 → factHelper 24 0 → 24

List Processing

Functional languages process lists recursively using head/tail patterns:

  • head: first element of a list — head [1,2,3] = 1
  • tail: all elements except the first — tail [1,2,3] = [2,3]
-- Sum a list recursively sumList [] = 0 -- base case: empty list sumList (x:xs) = x + sumList xs -- x is head, xs is tail sumList [1,2,3] -- = 1 + sumList [2,3] -- = 1 + 2 + sumList [3] -- = 1 + 2 + 3 + sumList [] -- = 1 + 2 + 3 + 0 = 6

Lazy Evaluation

Lazy evaluation (also called call-by-need): expressions are not evaluated until their value is actually needed. This allows:

  • Infinite data structures (e.g. an infinite list of natural numbers)
  • Efficiency — avoids computing values that are never used
-- Haskell: infinite list of natural numbers (lazy) nats = [1..] -- infinite list, never fully evaluated take 5 nats -- [1, 2, 3, 4, 5] — only first 5 evaluated
Exam tip: AQA 7517 requires: (1) explain closure and trace an example; (2) explain currying/partial application — know that currying transforms multi-arg functions into single-arg chains; (3) trace recursive functions with base case + recursive case; (4) explain tail recursion and why it avoids stack overflow; (5) trace list processing with head/tail. These topics appear as trace + explain questions worth 4–6 marks.
Click through the slides at your own pace. Use arrow keys or click to advance.
Click slide or press arrow keys to navigate

Worksheet — 4.12.1b Functional Programming in Practice

8 questions · instantly marked · AQA 7517 standard

Q1What is a closure in functional programming? Explain using an example.[2]
✅ Mark scheme
Mark scheme
A closure is a function that captures and retains access to variables from the scope in which it was defined — even after that scope has finished executing [1]; the function "closes over" the free variables [1]. Example: makeAdder n = \x -> n + x; add5 = makeAdder 5 [1]; add5 3 returns 8 — add5 remembers n=5 even though makeAdder has returned [1].
Q2Explain what currying is. How does it differ from partial application?[3]
✅ Mark scheme
Mark scheme
Currying: transforming a function that takes multiple arguments into a chain of functions each taking a single argument [1]; e.g. add x y = x+y is curried — add 3 returns a function \y->3+y [1]. Partial application: supplying fewer arguments than a function expects to produce a new function [1]; e.g. triple = multiply 3 — multiply is partially applied with 3, producing a function that triples its argument [1].
Q3Trace factorial 3 using the recursive definition. Show every step.[2]
✅ Mark scheme
Mark scheme
factorial 3 = 3 * factorial 2 [1]; = 3 * (2 * factorial 1) [1]; = 3 * (2 * (1 * factorial 0)) [1]; = 3 * 2 * 1 * 1 = 6 [1]. Base case: factorial 0 = 1.
Q4Explain what tail recursion is and why it is more efficient than regular recursion.[3]
✅ Mark scheme
Mark scheme
Tail recursion: the recursive call is the very last operation in the function — no computation is done after the recursive call returns [1]. Regular recursion builds up a stack of pending return values — for large inputs this causes stack overflow [1]. Tail recursion allows tail call optimisation (TCO): the compiler replaces the recursive call with a loop — the stack frame is reused rather than a new one pushed for each call [1]; this uses O(1) stack space instead of O(n), preventing stack overflow [1].
Q5Trace sumList [1, 2, 3] using: sumList [] = 0; sumList (x:xs) = x + sumList xs[3]
✅ Mark scheme
Mark scheme
sumList [1,2,3]: x=1, xs=[2,3] → 1 + sumList [2,3] [1]; sumList [2,3]: x=2, xs=[3] → 2 + sumList [3] [1]; sumList [3]: x=3, xs=[] → 3 + sumList [] [1]; sumList [] = 0; unwind: 3+0=3, 2+3=5, 1+5=6. Result: 6 [1].
Q6In Haskell: multiply x y = x * y. What does triple = multiply 3 give? What is triple 7?[2]
✅ Mark scheme
Mark scheme
triple = multiply 3 is partial application — multiplying 3 is supplied, returning a new function that multiplies its argument by 3 [1]; triple y = 3 * y [1]; triple 7 = 3 * 7 = 21 [1].
Q7What is lazy evaluation? Give one advantage it provides in functional programming.[2]
✅ Mark scheme
Mark scheme
Lazy evaluation: expressions are not evaluated until their value is actually needed (call-by-need) [1]. Advantage (any 1): allows infinite data structures — only the portion actually needed is evaluated; e.g. take 5 [1..] works even though [1..] is an infinite list [1]; efficiency — avoids computing values that are never used by the rest of the program [1].
Q8Define a recursive function length' that returns the number of elements in a list. Trace length' [4, 7, 2] and give the result.[3]
✅ Mark scheme
Mark scheme
Definition: length' [] = 0 [1]; length' (_:xs) = 1 + length' xs [1] (the head element is discarded with _ since we only count, not use it). Trace: length' [4,7,2] = 1 + length' [7,2] [1]; = 1 + 1 + length' [2] = 1 + 1 + 1 + length' [] = 1+1+1+0 [1]; Result: 3 [1].
Functional Programming in Practice Quiz
Question 1 of 15
You scored
out of 15
Card 1 of 8
Click to reveal definition
🎉
All cards reviewed!
TermDefinition
🎯

Mini Test — Closures, Currying & Recursion

10 questions · 10 minutes

← 4.12.1a Functional Programming
70 of 70 · AQA 7517
4.13.1 Problem Solving →