Lesson 12 / 25
Point-Free Style and Its Limits
Defining functions without naming their arguments.
Elegant until it is not
Point-free (or tacit) style defines a function by composing other functions without mentioning its arguments: const shout = pipe(toUpper, exclaim) instead of s => exclaim(toUpper(s)). It removes noise when the pieces have clear names. It hurts when readers must mentally track argument order through flip, nested composition or library combinators, and stack traces show anonymous wrappers. In JavaScript it also has a classic trap: passing a function with optional extra parameters to map, such as ["1","2","3"].map(parseInt), which passes the index as the radix.
Readable and unreadable point-free code
And the parseInt trap.
const toUpper = (s: string) => s.toUpperCase();
const exclaim = (s: string) => `${s}!`;
const pipe2 = <A, B, C>(f: (a: A) => B, g: (b: B) => C) => (a: A): C => g(f(a));
// Point-free: no named argument
const shout = pipe2(toUpper, exclaim);
// Pointed equivalent: (s: string) => exclaim(toUpper(s))
const names = ['ada', 'alan'].map(shout); // passing shout point-free is fine
// Trap: map passes (value, index, array)
const broken = ['1', '2', '3'].map(parseInt);
// parseInt('2', 1) and parseInt('3', 2) are NaN, so broken is [1, NaN, NaN]
// Explicit lambda states intent
const numbers = ['1', '2', '3'].map(s => parseInt(s, 10)); // [1, 2, 3]Name the intermediate steps
If a point-free chain needs a comment to explain it, give the argument a name or split the chain into named helper functions.
Quick check: Why does `["1","2","3"].map(parseInt)` misbehave?
- parseInt is not a function
- map also passes the index, which parseInt treats as the radix
- map skips the first element
- Strings cannot be mapped
Answer
map also passes the index, which parseInt treats as the radix — Point-free passing forwards every argument the caller supplies.