All A Level 2 45 min read 10 question quiz 17 exam marks

Functional Programming

Functions as values, map, filter and reduce, pure functions and immutability, composition, partial application and closures: the whole of the functional paradigm in the A Level specification, in working Python.

Functions are values

In imperative programming, a program is a sequence of commands that change variables. In functional programming, a program is built out of functions, in the maths sense: give one the same input and it always gives the same output, and it does nothing else.

The idea that makes this work is that a function is a first-class object. It can be treated like any other value:

  • assigned to a variable
  • stored in a list or a dictionary
  • passed to another function as an argument
  • returned from a function as a result
Python
def double(x):
    return x * 2

fn = double                  # no brackets: the function itself, not a call to it
print(fn(5))                 # 10

operations = [double, abs, str]
for op in operations:
    print(op(-3))            # -6, then 3, then -3

Function types

A function takes values from one set, the domain, and gives back values from another, the codomain. The specification writes this as a function type:

FunctionWritten asMeaning
doubleint → inttakes an integer, gives an integer
lenstr → inttakes a string, gives an integer
add(a, b)int × int → inttakes two integers, gives an integer
is_evenint → booltakes an integer, gives a Boolean

Python can record this in the code itself, with type hints. They do not change how the program runs, but they state the function type:

Python
def add(a: int, b: int) -> int:
    return a + b

print(add(2, 3))             # 5

Function application is simply the act of giving a function its argument: double(5) is the application of double to 5.

Higher order functions

A function is higher order if it takes a function as an argument, or returns one. It is what first class objects make possible.

Python
def apply_twice(f, x):
    return f(f(x))

print(apply_twice(lambda n: n * 2, 3))                # 12
print(apply_twice(lambda s: s + "!", "hi"))            # hi!!

lambda makes a small anonymous function in one expression: lambda n: n * 2 is the function that takes n and returns n * 2. It is the same thing as a def, without a name.

Map, filter and reduce

Three higher order functions do nearly everything a loop over a list is used for. Each takes a function and a list.

Map, filter and reduceThree higher order functions. Map applies the function n times n to every item of 1, 2, 3, 4 and gives 1, 4, 9, 16, a list of the same length. Filter applies the test n mod 2 equals 0 to 1 to 6 and keeps only 2, 4 and 6. Reduce combines 1, 2, 3, 4 with a plus b, two at a time: 1 plus 2 is 3, 3 plus 3 is 6, 6 plus 4 is 10, so it returns the single value 10.Three higher order functions, each given a function to applymap1234n * n14916applies the function to every item: the same length comes outfilter123456n % 2 == 0246keeps the items for which the function is True: the same or fewer come outreduce1234a + b101 + 2 = 33 + 3 = 66 + 4 = 10combines the items two at a time into one value
Each takes a function as an argument. That is what makes them higher order.
FunctionDoesReturns
map(f, items)applies f to every itema new list of the results, the same length
filter(f, items)keeps the items for which f returns Truea new list, the same length or shorter
reduce(f, items)combines the items, two at a time, with fa single value
Python
from functools import reduce

numbers = [1, 2, 3, 4, 5, 6]

squares = list(map(lambda n: n * n, numbers))
evens = list(filter(lambda n: n % 2 == 0, numbers))
total = reduce(lambda a, b: a + b, numbers)

print(squares)               # [1, 4, 9, 16, 25, 36]
print(evens)                 # [2, 4, 6]
print(total)                 # 21
print(numbers)               # [1, 2, 3, 4, 5, 6], untouched

None of the three changes numbers. Each builds something new. That is deliberate, and it is the next section.

How reduce works

reduce carries a running result along the list. It starts with the first two items, then combines that with the third, and so on. Printing inside the function shows it:

Python
from functools import reduce

def add_and_show(a, b):
    print(a, "+", b, "=", a + b)
    return a + b

print(reduce(add_and_show, [1, 2, 3, 4]))

Chaining them

The output of one is the input of the next, so they combine into a pipeline. The sum of the squares of the even numbers:

Python
from functools import reduce

numbers = [1, 2, 3, 4, 5, 6]

evens = filter(lambda n: n % 2 == 0, numbers)
squares = map(lambda n: n * n, evens)
total = reduce(lambda a, b: a + b, squares)
print(total)                 # 56: 4 + 16 + 36

Python programmers often use a list comprehension for the same job. It is the same idea in a different spelling:

Python
numbers = [1, 2, 3, 4, 5, 6]

print([n * n for n in numbers])                    # map
print([n for n in numbers if n % 2 == 0])          # filter
print(sum(n * n for n in numbers if n % 2 == 0))   # filter, map and reduce
Describe what each one does, with its result type

A typical mark scheme wants: map returns a list of the same length with the function applied to each item, filter returns the items for which the function was true, reduce returns one value. Say that each takes a function as an argument, because that is what makes it higher order.

Pure functions, immutability and lists

A function is pure if:

  1. it always returns the same output for the same input, and
  2. it has no side effects: it does not change anything outside itself, such as a global variable, a list it was given, a file or the screen.
Python
def add_item_impure(basket, item):
    basket.append(item)          # changes the caller's own list: a side effect
    return basket

def add_item_pure(basket, item):
    return basket + [item]       # builds a new list and leaves the old one alone

original = ["milk"]
add_item_impure(original, "eggs")
print(original)                  # ['milk', 'eggs'], changed behind our back

original = ["milk"]
new_basket = add_item_pure(original, "eggs")
print(original, new_basket)      # ['milk'] ['milk', 'eggs']

A pure function has referential transparency: every call can be replaced by the value it returns, and the program means the same. square(4) can be swapped for 16 anywhere.

Why bother

BenefitBecause
Easy to testSame input, same output, with nothing else to set up or check
Easy to reason aboutA bug cannot be caused by a hidden change from somewhere else
Safe in parallelTwo calls cannot interfere with each other
CacheableA result can be remembered, as memoisation does, since it never changes

The cost is that creating new values rather than changing old ones uses more memory and time.

Immutability

Functional programs prefer immutable data, which cannot change once created. In Python, tuples and strings are immutable:

Python
# raises: TypeError
point = (3, 4)
point[0] = 9

You make a changed copy instead: point = (9,) + point[1:].

Lists as head and tail

In functional languages a list is built from the head, its first item, and the tail, the list of everything else. A list with nothing in it is the empty list, and cons builds a new list by putting an item on the front.

Python
def head(items):
    return items[0]

def tail(items):
    return items[1:]

def cons(item, items):
    return [item] + items

def length(items):
    if items == []:
        return 0
    return 1 + length(tail(items))

def total(items):
    if items == []:
        return 0
    return head(items) + total(tail(items))

print(head([4, 5, 6]), tail([4, 5, 6]), cons(3, [4, 5, 6]))     # 4 [5, 6] [3, 4, 5, 6]
print(length([4, 5, 6]), total([4, 5, 6]))                      # 3 15

There is no loop and no variable that changes. The list is taken apart by recursion, which is how a language with no loops repeats anything.

Composition, partial application and closures

Composition

Composition joins two functions so that the output of one becomes the input of the next. Written in maths as f ∘ g, it means "apply g, then apply f".

Python
def compose(f, g):
    return lambda x: f(g(x))

add_one = lambda x: x + 1
double = lambda x: x * 2

double_then_add = compose(add_one, double)     # first double, then add one
add_then_double = compose(double, add_one)     # first add one, then double

print(double_then_add(5))        # 11
print(add_then_double(5))        # 12

Notice that compose is itself a higher order function: it takes two functions and returns a new one. And the order matters. f ∘ g is generally not the same as g ∘ f.

Partial application

Partial application fixes some of a function's arguments and gives back a new function that waits for the rest.

Python
from functools import partial

def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5), cube(2))        # 25 8

square is a function from one number to one number, made from a function of two numbers by supplying one of them in advance.

Closures and currying

A function written inside another can remember the variables of the function that made it, even after that function has finished. That is a closure.

Python
def add(a):
    def inner(b):
        return a + b
    return inner

add5 = add(5)
print(add5(3))                   # 8
print(add(2)(10))                # 12

add5 has not been given a since it was created, but it still has it. add is a curried function: instead of taking two arguments at once, it takes one and returns a function that takes the other. add(2)(10) applies them one after the other.

A closure can hold changing state too, which is how a function with a memory is built without a class:

Python
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

first = make_counter()
second = make_counter()
print(first(), first(), first(), second())     # 1 2 3 1

Each counter has its own count, hidden inside its closure where nothing else can reach it.

Summary of the paradigm

The defining ideas

Functions are first class objects. Programs are built by applying and composing functions, and by passing functions to higher order functions such as map, filter and reduce. Data is immutable and functions are pure, so a function's result depends only on its arguments. Repetition is done by recursion, not by changing a counter.

Key terms you must be able to define

First-class object
A value that can be assigned to a variable, stored in a data structure, passed as an argument and returned from a function. In functional languages, functions are first-class.
Higher-order function
A function that takes one or more functions as arguments, or returns a function as its result.
Map
A higher-order function that applies a function to every item of a list and returns the list of results.
Filter
A higher-order function that returns the items of a list for which a given function returns true.
Reduce
A higher-order function that combines all the items of a list into a single value by repeatedly applying a function.
Pure function
A function that always gives the same result for the same arguments and has no side effects.
Side effect
Any change a function makes outside itself, such as altering a global variable, its arguments, a file or the display.
Immutable
Describes data that cannot be changed once it has been created.
Composition
Combining two functions so that the output of one is the input of the other.
Partial application
Supplying some of a function's arguments to produce a new function that takes the remaining ones.
Closure
A function together with the variables of the surrounding scope that it can still use after that scope has ended.

Flashcards

Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.

DefineFirst-class object
AnswerA value that can be assigned to a variable, stored in a data structure, passed as an argument and returned from a function. In functional languages, functions are first-class.
DefineHigher-order function
AnswerA function that takes one or more functions as arguments, or returns a function as its result.
DefineMap
AnswerA higher-order function that applies a function to every item of a list and returns the list of results.
DefineFilter
AnswerA higher-order function that returns the items of a list for which a given function returns true.
DefineReduce
AnswerA higher-order function that combines all the items of a list into a single value by repeatedly applying a function.
DefinePure function
AnswerA function that always gives the same result for the same arguments and has no side effects.
DefineSide effect
AnswerAny change a function makes outside itself, such as altering a global variable, its arguments, a file or the display.
DefineImmutable
AnswerDescribes data that cannot be changed once it has been created.

Reach the top grade on this topic

Functional programming is a paradigm question: you are marked on defining each idea exactly and on using it.

  • Define first-class, higher-order, pure and immutable separately and precisely, with an example of each
  • Give the type of map, filter and reduce results: a list, a shorter list, and a single value
  • Trace a pipeline stage by stage, writing down the intermediate list after each one
  • Connect purity to benefits: testing, reasoning, parallel processing and caching
  • Remember that composition order matters, and apply the right-most function first

Mistakes that cost marks here

Avoid these
  • Saying map changes the original list, when it builds a new one.
  • Describing a function that prints, or changes a global, as pure.
  • Applying composed functions in the wrong order.
  • Confusing a higher-order function with any function that is complicated.
  • Forgetting that reduce returns one value, not a list.
Fun fact

The lambda in Python's lambda keyword comes from the lambda calculus, a system for describing computation using only functions that Alonzo Church published in the 1930s. It is equivalent in power to Alan Turing's machine, which Turing proved in 1937.

Knowledge check

0 of 10 answered
1What does it mean for a function to be a first-class object?
2What does map(f, items) return?
3What does filter(f, items) return?
4What does reduce(lambda a, b: a + b, [1, 2, 3, 4]) return?
5Which describes a pure function?
6Which of these is a side effect?
7Why is immutable data useful in functional programming?
8What are head([4, 5, 6]) and tail([4, 5, 6])?
9What is partial application?
10What is composition of two functions?
Score 0 / 10

Exam-style questions

Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.

17 marks
1State what is meant by a first-class object.2 marks
State

Mark scheme0 / 2
  • LA value that can be assigned to a variable
  • LIt can be passed as an argument to, or returned as the result of, a function
Model answer

A first-class object is a value that can be treated like any other data. It can be assigned to a variable or stored in a data structure, passed as an argument to a function and returned as the result of a function. In functional programming, functions are first-class objects.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
2Describe what the higher-order function filter does.3 marks
Describe

Mark scheme0 / 3
  • LIt takes a function and a list as arguments
  • LThe function is applied to each item and returns true or false
  • LIt returns a new list containing only the items for which the function returned true
Model answer

Filter takes a function that returns true or false, and a list. It applies the function to each item in the list, and returns a new list that contains only those items for which the function returned true. The original list is not changed.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
3The program shown is run. Show the result of each stage of the pipeline and state the value that is output.4 marks
Show
Python
from functools import reduce

nums = [3, 8, 1, 6]
result = reduce(lambda a, b: a + b,
                map(lambda n: n * 2,
                    filter(lambda n: n > 2, nums)))
print(result)

Mark scheme0 / 4
  • Lfilter keeps 3, 8 and 6
  • Lmap doubles them to 6, 16 and 12
  • Lreduce adds them together
  • LThe output is 34
Model answer

filter keeps the numbers greater than 2, which gives 3, 8 and 6. map doubles each of them, giving 6, 16 and 12. reduce then adds the values together, 6 + 16 = 22 and 22 + 12 = 34, so the value output is 34.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
4Explain why a pure function is easier to test than a function that has side effects.4 marks
Explain

Mark scheme0 / 4
  • LA pure function always returns the same output for the same input
  • LSo a test only needs to supply the arguments and check the returned value
  • LA function with side effects may change or depend on something outside it, such as a global variable or file
  • LSo the test would have to set up and check that outside state as well, and results may differ between runs
Model answer

A pure function always returns the same output for the same arguments and does nothing else, so a test only has to supply the arguments and compare the returned value with what is expected. A function with side effects may depend on or change something outside itself, such as a global variable or a file, so a test would also have to set up that state beforehand and check it afterwards, and the same call might give different results on different runs.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
5Write a Python function called make_multiplier that takes a number n and returns a new function. The new function takes a value x and returns x multiplied by n. Show how it could be used to create a function that triples a number.4 marks
Write

Mark scheme0 / 4
  • LDefines make_multiplier with parameter n
  • LDefines an inner function, or a lambda, that takes x
  • LThe inner function returns x * n, using n from the enclosing function
  • LThe outer function returns the inner function, and triple = make_multiplier(3) is used
Model answer
Python
def make_multiplier(n):
    def multiply(x):
        return x * n
    return multiply

triple = make_multiplier(3)
print(triple(7))

make_multiplier returns the inner function multiply, which still has access to n after make_multiplier has finished. That is a closure. triple is a function that multiplies by 3, so triple(7) outputs 21.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
Machine mark 0 / 17