Lesson 11 / 25
Currying and Partial Application
Fixing some arguments now, the rest later.
Two related ideas
Currying turns a function of several arguments into a chain of one-argument functions: add(a, b) becomes add(a)(b). Partial application fixes some arguments of a function and returns a function waiting for the rest; currying makes partial application trivial, and Function.prototype.bind or a small helper can do it too. Both help produce the unary functions that composition needs. In Haskell every function is curried by default; in JavaScript and TypeScript you opt in, and deep generic curry helpers are hard to type well.
Curried helpers in a pipeline
Data-last parameters make functions easy to compose.
// Curried, data-last helpers
const map = <A, B>(fn: (a: A) => B) => (xs: readonly A[]): B[] => xs.map(fn);
const filter = <A>(pred: (a: A) => boolean) => (xs: readonly A[]): A[] => xs.filter(pred);
const isEven = (n: number) => n % 2 === 0;
const square = (n: number) => n * n;
const evenSquares = (xs: number[]) => map(square)(filter(isEven)(xs));
// evenSquares([1, 2, 3, 4]) evaluates to [4, 16]
// Partial application without currying
const greet = (greeting: string, name: string) => `${greeting}, ${name}!`;
const hello = (name: string) => greet('Hello', name);
const hi = greet.bind(null, 'Hi'); // also partial applicationPython and Haskell
In Python, functools.partial(greet, "Hello") returns a function waiting for name. In Haskell every function is curried, so with add a b = a + b the expression add 1 is already a partially applied function.
Quick check: Why put the data argument last in curried helpers?
- So that the function becomes impure
- Because JavaScript requires it
- To make functions run faster
- So that fixing the configuration arguments yields unary functions ready to compose
Answer
So that fixing the configuration arguments yields unary functions ready to compose — map(square) is a function waiting for the data.