Function composition
Composing Two Functions
Section titled “Composing Two Functions”The simplest case — combining two functions into one:
# f ∘ g means: apply g first, then fdef compose(f, g): return lambda x: f(g(x))
double = lambda x: x * 2add_one = lambda x: x + 1
double_then_add = compose(add_one, double) # add_one(double(x))add_then_double = compose(double, add_one) # double(add_one(x))
print(double_then_add(5)) # 11 → double(5)=10, then add_one(10)=11print(add_then_double(5)) # 12 → add_one(5)=6, then double(6)=12Order matters — compose(f, g) applies g first and f second, just like mathematical notation.
Composing Any Number of Functions
Section titled “Composing Any Number of Functions”Two directions are possible — right to left (mathematical convention) and left to right (pipeline convention). Both use reduce() to chain an arbitrary number of functions together:
from functools import reduce
def compose_all(*funcs): """Right to left: compose_all(f, g, h)(x) = f(g(h(x)))""" return reduce(compose, funcs)
def pipe_all(*funcs): """Left to right: pipe_all(f, g, h)(x) = h(g(f(x)))""" return reduce(lambda f, g: lambda x: g(f(x)), funcs)pipe_all is often more readable in practice — the functions are listed in the order they are applied, which matches how you would describe the transformation in plain English:
normalize = pipe_all(str.strip, str.lower) # strip first, then lowercase
print(normalize(" Hello World ")) # "hello world"A Practical Text Processing Pipeline
Section titled “A Practical Text Processing Pipeline”Composition really shows its value when building multi-step data transformation pipelines. Each step is a small, focused function — composition wires them together:
import refrom functools import reduce
def compose(f, g): return lambda x: f(g(x))
def pipe_all(*funcs): return reduce(lambda f, g: lambda x: g(f(x)), funcs)
# Each step is small and testable on its ownremove_punctuation = lambda s: re.sub(r"[^\w\s]", "", s)normalize_whitespace = lambda s: re.sub(r"\s+", " ", s)
# Wire them together into a single reusable functionclean_text = pipe_all( str.strip, # 1. remove leading/trailing whitespace str.lower, # 2. lowercase everything remove_punctuation, # 3. strip punctuation normalize_whitespace, # 4. collapse multiple spaces into one)
print(clean_text(" Hello, World!!! ")) # "hello world"Each step in the pipeline is independently readable and testable. Adding or removing a step is a one-line change. This is the practical payoff of function composition — complexity built from simplicity.