dryrun

Currying and Partial Application

javascript

Currying: Transforming Functions

Currying is a transformation that restructures a multi-argument function into a sequence of nested single-argument functions.

The transformation looks like this:

// Before currying: one function taking multiple arguments
function add(a, b, c) {
  return a + b + c;
}

// After currying: nested functions, each taking one argument
function curriedAdd(a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}

// Or with arrow functions:
const curriedAdd = (a) => (b) => (c) => a + b + c;

Notice the structure: we’ve gone from one function with three parameters to three nested functions, each with one parameter. That’s the transformation.

Key insight: Currying changes the structure of a function. It’s a one-time transformation of how the function is defined.

Partial Application: Fixing Arguments

Partial Application is the act of calling a function with some (but not all) of its arguments, producing a new function that expects the remaining arguments.

const add = (a) => (b) => (c) => a + b + c;

// One argument at a time
const addTwo = add(2);
const addTwoAndThree = addTwo(3);
const result = addTwoAndThree(5); // 10

// Multiple arguments at once (with flexible curry implementation)
const addOneAndTwo = add(1, 2);
addOneAndTwo(3); // 6

Key insight: Partial application happens at call time. You’re not restructuring the function; you’re calling it with some arguments and getting back a function that expects the rest.

OK, but why?

Currying shines when you need to create specialized functions for APIs that expect a specific signature. Event handlers are a perfect example:

// Without currying: repetitive inline functions
<button onClick={(e) => { e.preventDefault(); dispatch('SUBMIT'); }} />
<button onClick={(e) => { e.preventDefault(); dispatch('CANCEL'); }} />
<button onClick={(e) => { e.preventDefault(); dispatch('RESET'); }} />

With currying, you can create a reusable handler factory:

const handleClick = (action) => (event) => {
  event.preventDefault();
  dispatch(action);
};

// Clean, reusable handlers
<button onClick={handleClick('SUBMIT')} />
<button onClick={handleClick('CANCEL')} />
<button onClick={handleClick('RESET')} />

The curried function lets you “bake in” the action while producing a function with the exact signature onClick expects: (event) => void.

This pattern extends naturally to data transformations:

const multiply = (a) => (b) => a * b;

const double = multiply(2);
const triple = multiply(3);

[1, 2, 3].map(double); // [2, 4, 6]
[1, 2, 3].map(triple); // [3, 6, 9]

Partial application lets you create specialized versions of functions without repeating arguments. This is powerful for building data pipelines and composing operations with map, filter, and reduce.

Implementation

Let’s implement a curry function that transforms any multi-argument function into a curried version. This implementation is flexible—like lodash’s curry—and allows multiple arguments to be passed at once.

The idea: check if we have enough arguments. Every function has a .length property showing how many parameters it expects. If we have enough arguments, call the original function. If not, return a new function that remembers the arguments received so far and waits for more. When called again, it combines old and new arguments and repeats the check.

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function (...args2) {
        return curried.apply(this, args.concat(args2));
      };
    }
  };
}

Dry Run: Tracing add(1)(2)(3)

Let’s trace through exactly what happens when we call our curried add function:

1/7Setup
First, we define our function and curry it:
const _add = (a, b, c) => a + b + c← 3 params
const add = curry(_add)← wrap it
function curry(func) {← called with _add
return function curried(...args) {← returns this
if (args.length >= func.length)
return func.apply(this, args)
else
return function (...args2) {
return curried(...args.concat(args2))
}
}
}
_add.length:3add:curried fn