Total Functions
A total function returns a valid result for every possible input of its type; a partial function only works for some inputs and crashes, throws or misbehaves on the others.
Also known as: Total Function, Partial Functions, Partial Function, Total Functional Programming
Category: Software Development
Tags: software-design, software-development, programming, functional-programming, type-systems, error-handling, error-prevention
Explanation
Look at a function signature like `divide(a: number, b: number): number`. It promises a number for any two numbers. But what happens when `b` is zero? The signature lies. That's a partial function: it's defined for some of its inputs, not all of them.
A total function keeps its promise. For every value its input type allows, it returns a valid value of its output type. No exceptions, no crashes, no infinite loops.
**Classic partial functions**:
- **`head` of a list** in Haskell: crashes on an empty list
- **Array indexing**: `items[10]` fails (or returns `undefined`) when the array is shorter
- **Integer division by zero**
- **Parsing**: `parseInt("abc")` returns `NaN` in JavaScript, and `int("abc")` raises an exception in Python
- **Dictionary lookups** that throw when the key is missing
- **Type casts** that blow up at runtime
**Why partial functions are dangerous**:
The failure case is invisible in the type. Nothing forces the caller to think about the empty list, the missing key or the zero divisor. So callers forget, and the bug shows up in production, far from the cause.
**How to make a function total**:
There are three main strategies:
1. **Narrow the input type**: Accept only the inputs that work. `head(list: NonEmptyList<T>): T` can never fail, because an empty list can't be passed (see Make Illegal States Unrepresentable)
2. **Widen the output type**: Make failure part of the result. `head(list: T[]): Option<T>` returns `None` for an empty list, and `parse(s): Result<number, ParseError>` returns an error value instead of throwing (see Option Type)
3. **Provide a sensible default**: `getOrDefault(key, fallback)` always returns something. Only use this when the default really makes sense, otherwise it hides bugs
**The benefits**:
- **Honest signatures**: The type tells you everything that can happen
- **The compiler helps**: It forces callers to handle the failure case
- **Easier reasoning**: Total, pure functions can be composed without wondering which one might explode
- **Fewer runtime surprises**: A whole category of crashes moves to compile time
**Total functional programming**:
Some languages go further. Agda, Idris and Lean can check that functions are total, including that they always terminate. The computer scientist David Turner made the case for "total functional programming", where every function must be total. Most everyday code doesn't need that level of rigor, but the habit of asking "what happens for EVERY input?" pays off in any language.
Total functions are a small, practical case of Correct by Construction: when every function handles every input, there's simply no crash path left to find.
Related Concepts
← Back to all concepts