Functional Programming and Pure Functions
A Shelfwise test for add_tax passes in the morning and fails in the evening. The code has not changed. The reason: add_tax quietly reads the current region from a global setting and writes the new total into the cart. Functional programming is a way of writing code where that kind of surprise cannot happen.
The rule
Same input, same output, and nothing else touched.
That describes a pure function:
- Same input, same output. Its result depends only on its arguments, not on the clock, a global or a database.
- Nothing else touched. It has no side effects: it does not change its arguments, write files, print, or save.
# Impure: hidden input and two side effects
def add_tax(cart):
cart.total = cart.total * (1 + TAX_RATES[current_region()])
save(cart)
# Pure: everything it needs comes in, one value goes out
def with_tax(total, rate):
return total * (1 + rate)Functional programming builds programs mostly out of pure functions, plus two more habits:
- Immutable data. Instead of changing a value, make a new one:
new_lines = lines + [line]rather thanlines.append(line). - Functions as values. Pass functions to other functions.
sorted(books, key=lambda b: b.price)handssorteda small function that says how to compare.
A program still has to save orders and send emails, so side effects do not vanish. They move to the edges: a thin layer of impure code gathers inputs, calls pure functions to do the thinking, then writes the results out.
The payoff is testing and reasoning. A pure function needs no setup and no mocks: call it, compare the answer. It can never break because of what ran before it.
The shortcut
Ask two questions about any function. Does it read anything besides its arguments? Does it change anything besides its return value? Two "no"s mean it is pure.
The code
def order_total(lines): return sum(l.price * l.qty for l in lines)
The trap is the hidden read. A function that calls datetime.now() or reads a config value looks innocent but fails question one.
Drills
Answer fast — trust the rule.
Which function is pure?
def price_now(book): return book.price * exchange_rate_today() — is it pure?
What is the main testing benefit of pure functions?
Which line adds a book to a list without changing the original list?
Remember
- Pure: same input, same output, and nothing else touched.
- Two questions: hidden inputs? hidden outputs? Two "no"s mean pure.
- Trap: the innocent-looking read of the clock or a global; push side effects to the edges.
