Lesson 6 of 8

Currying & Partial Application

Transform functions to accept arguments one at a time for flexibility and reuse

What is Currying?

Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument. Named after mathematician Haskell Curry, it's fundamental to functional programming and enables powerful composition patterns.

// Regular function with multiple arguments
const add = (a, b, c) => a + b + c;
add(1, 2, 3); // 6

// Curried version
const addCurried = a => b => c => a + b + c;
addCurried(1)(2)(3); // 6

// The magic: partial application
const add1 = addCurried(1);      // b => c => 1 + b + c
const add1and2 = add1(2);        // c => 1 + 2 + c
const result = add1and2(3);      // 6

// Or step by step
const addTo10 = addCurried(10);
const addTo10And5 = addTo10(5);
addTo10And5(3);  // 18

Currying vs Partial Application

  • Currying: Always produces unary (single-argument) functions. A function of n arguments becomes n functions of 1 argument.
  • Partial Application: Fixes some arguments of a function, producing a function with fewer arguments. More flexible about how many arguments to fix.
// CURRYING: unary functions only
const curriedAdd = a => b => c => a + b + c;
curriedAdd(1)(2)(3); // Must call one arg at a time

// PARTIAL APPLICATION: fix any number of arguments
const add = (a, b, c) => a + b + c;

// Using bind for partial application
const add1 = add.bind(null, 1);       // a=1 fixed
add1(2, 3); // 6

const add1and2 = add.bind(null, 1, 2); // a=1, b=2 fixed
add1and2(3); // 6

// Partial application helper
const partial = (fn, ...fixedArgs) =>
  (...remainingArgs) => fn(...fixedArgs, ...remainingArgs);

const add5 = partial(add, 5);
add5(10, 20); // 35

Implementing curry

// Simple curry function
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
}

// Usage
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);

curriedAdd(1)(2)(3);     // 6
curriedAdd(1, 2)(3);     // 6 - flexible!
curriedAdd(1)(2, 3);     // 6
curriedAdd(1, 2, 3);     // 6

// Arrow function version
const curry = fn =>
  function curried(...args) {
    return args.length >= fn.length
      ? fn(...args)
      : (...more) => curried(...args, ...more);
  };

Practical Currying Examples

// 1. Configuration functions
const log = level => message => 
  console.log(`[${level}] ${message}`);

const logError = log('ERROR');
const logInfo = log('INFO');
const logDebug = log('DEBUG');

logError('Something went wrong!');
logInfo('User logged in');

// 2. Event handlers
const handleEvent = eventType => handler => element => {
  element.addEventListener(eventType, handler);
  return () => element.removeEventListener(eventType, handler);
};

const onClick = handleEvent('click');
const onSubmit = handleEvent('submit');

const removeClickHandler = onClick(() => console.log('Clicked!'))(button);

// 3. API helpers
const api = baseUrl => endpoint => options =>
  fetch(`${baseUrl}${endpoint}`, options).then(r => r.json());

const myApi = api('https://api.example.com');
const getUsers = myApi('/users');
const getPosts = myApi('/posts');

// Call with options
getUsers({ headers: { 'Authorization': 'Bearer token' } });

// 4. Data transformation
const map = fn => arr => arr.map(fn);
const filter = pred => arr => arr.filter(pred);
const prop = key => obj => obj[key];

const getNames = map(prop('name'));
const getActiveUsers = filter(prop('active'));

const users = [
  { name: 'Alice', active: true },
  { name: 'Bob', active: false },
];

getNames(users); // ['Alice', 'Bob']
getActiveUsers(users); // [{ name: 'Alice', active: true }]

Currying for Composition

// Curried functions compose beautifully
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

// Curried utilities
const map = fn => arr => arr.map(fn);
const filter = pred => arr => arr.filter(pred);
const sort = compareFn => arr => [...arr].sort(compareFn);
const take = n => arr => arr.slice(0, n);
const prop = key => obj => obj[key];

// Build a data processing pipeline
const processUsers = pipe(
  filter(user => user.age >= 18),
  sort((a, b) => a.name.localeCompare(b.name)),
  take(5),
  map(prop('name'))
);

const users = [
  { name: 'Charlie', age: 25 },
  { name: 'Alice', age: 17 },
  { name: 'Bob', age: 30 },
  { name: 'Diana', age: 22 },
];

processUsers(users); // ['Bob', 'Charlie', 'Diana']

// Compare to non-curried
users
  .filter(user => user.age >= 18)
  .sort((a, b) => a.name.localeCompare(b.name))
  .slice(0, 5)
  .map(user => user.name);

// The curried version is more reusable!
// processUsers can be used anywhere

Placeholder Currying

// Sometimes you want to fix arguments out of order
// Ramda uses R.__ as placeholder

// Simple placeholder implementation
const _ = Symbol('placeholder');

function curryWithPlaceholder(fn) {
  return function curried(...args) {
    const hasPlaceholder = args.some(arg => arg === _);
    const complete = args.length >= fn.length && !hasPlaceholder;
    
    if (complete) return fn(...args);
    
    return (...more) => {
      const merged = args.map(arg => 
        arg === _ && more.length ? more.shift() : arg
      );
      return curried(...merged, ...more);
    };
  };
}

const divide = (a, b) => a / b;
const curriedDivide = curryWithPlaceholder(divide);

const divideBy2 = curriedDivide(_, 2);  // Fix second arg
divideBy2(10); // 5

const divide10By = curriedDivide(10, _); // Fix first arg  
divide10By(2); // 5

Currying in React

// Event handler factories
function TodoList({ todos, onToggle, onDelete }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input 
            type="checkbox"
            checked={todo.done}
            onChange={handleToggle(todo.id)}
          />
          {todo.text}
          <button onClick={handleDelete(todo.id)}>
            Delete
          </button>
        </li>
      ))}
    </ul>
  );
  
  // Curried handlers
  function handleToggle(id) {
    return () => onToggle(id);
  }
  
  function handleDelete(id) {
    return () => onDelete(id);
  }
}

// HOC pattern with currying
const withAuth = requiredRole => Component => props => {
  const { user } = useAuth();
  
  if (!user || user.role !== requiredRole) {
    return <Navigate to="/login" />;
  }
  
  return <Component {...props} />;
};

const AdminDashboard = withAuth('admin')(Dashboard);
const UserProfile = withAuth('user')(Profile);

⚠️ When to Use Currying

  • ✅ When you need to create specialized functions from general ones
  • ✅ When composing functions in pipelines
  • ✅ When creating event handler factories
  • ✅ When building configuration-based functions
  • ❌ Don't overcurry - sometimes explicit arguments are clearer
  • ❌ Be careful with 'this' context in curried methods