You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
This synthesis lesson pulls the whole functional-programming topic together through full worked exam-style questions. Rather than re-teach the material, it shows you how the separate threads — the paradigm's characteristics, higher-order functions, map/filter/fold, recursion, composition and big data — combine in the kinds of question AQA actually sets, and what separates a mid-band answer from a top-band one. Each question below carries an AO breakdown and three banded model answers (Mid / Stronger / Top) with examiner-style commentary, so you can see precisely where marks are won and lost.
Work each question yourself before reading the answers. The single most common way to lose marks in this topic is to assert a result without showing the working — so wherever a question says "trace", write every reduction step, exactly as the model answers do.
This synthesis lesson spans the whole of AQA 7517 §4.12 (Fundamentals of functional programming) and §4.11 (Big Data). The specimen questions collectively exercise: the characteristics of the functional paradigm and its contrast with the imperative/OOP paradigms (§4.12.1); pure functions, side effects, referential transparency and immutability (§4.12.1); first-class and higher-order functions (§4.12.2); the list-processing functions map, filter and fold and their composition (§4.12.3); recursion with base and recursive cases; partial application and function composition; and the three Vs, MapReduce and the functional model for big data (§4.11). The aim is not new content but assessment fluency — recognising which assessment objective (AO1 knowledge, AO2 application, AO3 evaluation) a question targets and answering at the right depth.
The explanations here are our own restatement of the assessable content, not a verbatim quotation of AQA's specification.
A company is rewriting a data-analysis tool currently written in an imperative style with many shared mutable variables. The tool must scale across many processors.
(a) State two characteristics that distinguish the functional paradigm from the imperative paradigm. (2 marks) (b) Explain one way a functional rewrite could make the tool easier to run across many processors. (3 marks) (c) Discuss whether a functional approach is always the best choice for software of this kind. (4 marks)
AO breakdown:
(a) Data is immutable and functions have no side effects. (b) Because nothing can be changed, the processors will not interfere with each other, so you can split the work up. (c) Functional programming is good for testing and parallelism but harder to learn and can be slower, so it is not always best.
(a) (i) Data is immutable — values cannot change once created. (ii) Functions are pure — same input, same output, no side effects. (b) Race conditions happen when threads write to shared mutable state. With immutable data there is no shared cell to overwrite, so processors can read the same data at once with no locks, making the analysis straightforward to parallelise. (c) Strengths: safe parallelism and easy testing. Limitations: immutable updates can use more memory and the team faces a learning curve. For a scalable analysis tool the parallelism benefits likely outweigh the costs, so a functional approach is justified here.
(a) (i) Immutability — once bound, a value cannot be reassigned; "updates" produce new values. (ii) Purity / no side effects — a function's result depends only on its arguments and it changes no external state. (b) The defining benefit is referential transparency arising from purity and immutability: an independent pure computation neither reads nor writes shared state, so it cannot participate in a race condition. Race conditions stem specifically from concurrent writes to shared mutable memory; eliminating mutation removes the hazard at source, letting the runtime distribute independent pure tasks across processors without locks (and so without the deadlocks locks can cause) — the pattern MapReduce exploits. (c) The functional approach strongly serves this tool: safe parallelism, trivially testable pure functions and equational reasoning all target a scalable analysis workload. The trade-offs are real but bounded — immutable updates raise allocation cost (mitigated by structural sharing) and lazy evaluation can make space usage harder to predict, while a team new to the style faces a one-off learning curve. Because the dominant requirement is correct, scalable analysis, the paradigm's advantages are decisive; a pragmatic conclusion is a predominantly functional design, isolating any performance-critical inner loop for optimisation if profiling demands, rather than an absolutist claim that functional is always best.
Examiner-style commentary: The mid-band answer earns the recall marks but its (b) and (c) stay vague and unbalanced. The stronger answer names race conditions and locks and reaches a justified verdict. The top-band answer links purity and immutability through referential transparency, cites MapReduce, acknowledges lazy-evaluation space behaviour and structural sharing, and qualifies its conclusion — the AO3 discriminators.
A list of integers is to be processed.
(a) Explain the difference between first-class functions and higher-order functions, giving a Haskell example of each. (4 marks) (b) Using
filter,mapandfoldl, write a single Haskell expression that, given the list[3, 7, 2, 8, 1, 5, 9, 4], sums the doubles of the elements greater than 4. Show the value after each stage. (4 marks) (c) Rewrite your answer to (b) using function composition, and explain the order in which the composed functions are applied. (3 marks)
AO breakdown:
(a) First-class functions can be passed around like values. Higher-order functions take a function as an argument. Example: map (*2) xs. (b)
foldl (+) 0 (map (*2) (filter (>4) [3,7,2,8,1,5,9,4]))
-- = 58
(c) (foldl (+) 0 . map (*2) . filter (>4)). The dot joins the functions together so they run as one.
(a) First-class functions is a property of the language: functions are values that can be bound to names, passed as arguments, returned and stored, e.g. double = (*2). Higher-order functions is a property of a function: it takes and/or returns a function, e.g. map (*2) [1,2,3], where map takes the function (*2). First-class functions are the prerequisite for higher-order ones. (b)
foldl (+) 0 (map (*2) (filter (>4) [3, 7, 2, 8, 1, 5, 9, 4]))
-- filter (>4) -> [7, 8, 5, 9]
-- map (*2) -> [14, 16, 10, 18]
-- foldl (+) 0 -> 58
(c)
(foldl (+) 0 . map (*2) . filter (>4)) [3, 7, 2, 8, 1, 5, 9, 4] -- 58
Composition with . applies the functions right to left: filter (>4) first, then map (*2), then foldl (+) 0.
(a) First-class functions describes the language: a function may be used anywhere any other value can — bound to a name, passed as an argument, returned as a result, or stored in a data structure. Example: ops = [(*2), (+1)] stores functions in a list. Higher-order functions describes a particular function: one that takes one or more functions as arguments and/or returns a function. Example: map :: (a -> b) -> [a] -> [b], whose bracketed first argument is itself a function. The relationship is one-directional — first-class functions are necessary for higher-order functions (you cannot pass (*2) to map unless functions are passable), but merely naming a function (double = (*2)) uses first-class functions without being higher-order. (b)
foldl (+) 0 (map (*2) (filter (>4) [3, 7, 2, 8, 1, 5, 9, 4]))
-- filter (>4) [3,7,2,8,1,5,9,4] = [7, 8, 5, 9]
-- map (*2) [7,8,5,9] = [14, 16, 10, 18]
-- foldl (+) 0 [14,16,10,18] = (((0+14)+16)+10)+18 = 58
(c)
process :: [Int] -> Int
process = sum . map (*2) . filter (>4) -- sum is foldl (+) 0
process [3, 7, 2, 8, 1, 5, 9, 4] -- 58
(f . g) x is defined as f (g x), so composition reads right to left: the data flows through filter (>4) first, its result into map (*2), and that into sum. The point-free form removes the explicit argument and names the pipeline of transformations, which is both concise and reusable; the left-to-right reading order of the source (sum . map . filter) is the reverse of the data-flow order, a common trap.
Examiner-style commentary: The mid-band (a) blurs the language-vs-function distinction and (c) does not state the right-to-left order. The stronger answer gets the distinction, the staged trace and the application order. The top-band answer adds the one-directional prerequisite relationship, shows the foldl accumulation explicitly, gives the (f . g) x = f (g x) definition, and flags the source-order/data-order reversal — precise, well-justified detail.
(a) Write a recursive Haskell function
countEvens :: [Int] -> Intthat returns how many elements of a list are even. (3 marks) (b) Trace the evaluation ofcountEvens [3, 4, 7, 2], showing each step. (4 marks) (c) Explain what the base case is in your function and why omitting it would cause a problem. (2 marks)
AO breakdown:
(a)
countEvens [] = 0
countEvens (x:xs) = if even x then 1 + countEvens xs else countEvens xs
(b) 3 is odd, 4 is even, 7 is odd, 2 is even, so the answer is 2. (c) The base case is countEvens [] = 0. Without it the function would keep going forever.
(a)
countEvens :: [Int] -> Int
countEvens [] = 0
countEvens (x:xs)
| even x = 1 + countEvens xs
| otherwise = countEvens xs
(b)
countEvens [3, 4, 7, 2]
= countEvens [4, 7, 2] -- 3 is odd
= 1 + countEvens [7, 2] -- 4 is even
= 1 + countEvens [2] -- 7 is odd
= 1 + (1 + countEvens []) -- 2 is even
= 1 + (1 + 0) -- base case
= 2
(c) The base case is countEvens [] = 0. It stops the recursion when the list is empty; without it, the recursive case would be applied to [], which it cannot match, and the recursion would never terminate.
(a)
countEvens :: [Int] -> Int
countEvens [] = 0 -- base case
countEvens (x:xs)
| even x = 1 + countEvens xs -- count this element, recurse on tail
| otherwise = countEvens xs -- skip it, recurse on tail
(b) The recursion winds down to the empty list, then the additions resolve back up:
countEvens [3, 4, 7, 2]
= countEvens [4, 7, 2] -- 3 odd: otherwise branch, no +1
= 1 + countEvens [7, 2] -- 4 even: guard True, contributes 1
= 1 + countEvens [2] -- 7 odd: otherwise branch
= 1 + (1 + countEvens []) -- 2 even: guard True, contributes 1
= 1 + (1 + 0) -- [] matches the base case, returns 0
= 1 + 1
= 2
(c) The base case is countEvens [] = 0: it gives a direct, non-recursive answer for the empty list. Every recursive call shortens the list by one element via the tail xs, so the argument strictly approaches [] — the base case is what the recursion converges to and where it stops. Omitting it would leave only the (x:xs) equation, which cannot match []; depending on the language this is either a pattern-match failure or unbounded recursion that exhausts the call stack. A correct recursive definition therefore needs both a base case that terminates and a recursive case that makes progress towards it.
Examiner-style commentary: The mid-band (b) merely states the answer with no working — a guaranteed loss of trace marks even though the result is right. The stronger answer shows every reduction and explains termination. The top-band answer additionally articulates the down-then-up shape, states the strictly-decreasing argument that guarantees convergence, and distinguishes pattern-match failure from stack exhaustion — the depth that secures full marks.
(a) State the three Vs of big data. (3 marks) (b) Explain how the MapReduce model relates to the functional concepts of
mapandreduce(fold), using a word-count example. (4 marks) (c) Explain why immutability and pure functions make MapReduce suitable for processing data across thousands of machines, including what happens when a machine fails. (4 marks)
AO breakdown:
map/fold via a worked example.(a) Volume, velocity, variety. (b) MapReduce uses map to process the data and reduce to combine it, like the functional map and fold. For word count, map turns words into pairs and reduce adds them up. (c) Because the data cannot change, many machines can use it at once without problems, so it works across lots of machines. If one fails the work can be done again.
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.