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 application

Python 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.