Currying and Partial Application
Currying
Section titled “Currying”Converting a function with multiple arguments into a sequence of functions, each taking a single argument.
Basic Currying
Section titled “Basic Currying”// Regular functionfunction add(a: number, b: number): number { return a + b;}
add(2, 3); // 5
// Curried versionfunction curriedAdd(a: number) { return (b: number): number => a + b;}
const add2 = curriedAdd(2);add2(3); // 5add2(5); // 7Generic Curry Function
Section titled “Generic Curry Function”// Curry two-argument functionfunction curry<A, B, C>( fn: (a: A, b: B) => C): (a: A) => (b: B) => C { return (a: A) => (b: B) => fn(a, b);}
// Usageconst multiply = (a: number, b: number) => a * b;const curriedMultiply = curry(multiply);
const triple = curriedMultiply(3);triple(4); // 12triple(5); // 15
// Or directlycurriedMultiply(3)(4); // 12Three-Argument Curry
Section titled “Three-Argument Curry”function curry3<A, B, C, D>( fn: (a: A, b: B, c: C) => D): (a: A) => (b: B) => (c: C) => D { return (a: A) => (b: B) => (c: C) => fn(a, b, c);}
// Usageconst sum3 = (a: number, b: number, c: number) => a + b + c;const curriedSum = curry3(sum3);
curriedSum(1)(2)(3); // 6
const add1 = curriedSum(1);const add1And2 = add1(2);add1And2(3); // 6Partial Application
Section titled “Partial Application”Pre-filling some arguments of a function without transforming its arity.
Basic Partial Application
Section titled “Basic Partial Application”function partial<A, B, C>( fn: (a: A, b: B) => C, a: A): (b: B) => C { return (b: B) => fn(a, b);}
// Usagefunction greet(greeting: string, name: string): string { return `${greeting}, ${name}!`;}
const sayHello = partial(greet, 'Hello');const sayGoodbye = partial(greet, 'Goodbye');
sayHello('John'); // "Hello, John!"sayHello('Jane'); // "Hello, Jane!"sayGoodbye('Bob'); // "Goodbye, Bob!"Multiple Arguments Partial
Section titled “Multiple Arguments Partial”function partialRight<A, B, C>( fn: (a: A, b: B) => C, b: B): (a: A) => C { return (a: A) => fn(a, b);}
// Usageconst divide = (a: number, b: number) => a / b;
const divideBy2 = partialRight(divide, 2);divideBy2(10); // 5divideBy2(20); // 10Practical Examples
Section titled “Practical Examples”Event Handlers
Section titled “Event Handlers”// Without currying - repetitivebutton1.addEventListener('click', () => handleClick('button1'));button2.addEventListener('click', () => handleClick('button2'));button3.addEventListener('click', () => handleClick('button3'));
// With currying - cleanerconst createClickHandler = (id: string) => () => handleClick(id);
button1.addEventListener('click', createClickHandler('button1'));button2.addEventListener('click', createClickHandler('button2'));button3.addEventListener('click', createClickHandler('button3'));API Calls
Section titled “API Calls”function apiRequest<T>( method: string, url: string, data?: any): Promise<T> { return fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: data ? JSON.stringify(data) : undefined }).then(r => r.json());}
// Create specialized functionsconst get = curry(apiRequest)('GET');const post = curry(apiRequest)('POST');const put = curry(apiRequest)('PUT');const del = curry(apiRequest)('DELETE');
// Usageawait get('/api/users');await post('/api/users', { name: 'John' });await put('/api/users/1', { name: 'Jane' });await del('/api/users/1');Validation
Section titled “Validation”function validate<T>( predicate: (value: T) => boolean, message: string, value: T): { valid: boolean; message?: string } { return predicate(value) ? { valid: true } : { valid: false, message };}
// Create validatorsconst curriedValidate = curry3(validate);
const validateEmail = curriedValidate( (email: string) => email.includes('@'))('Invalid email format');
const validateLength = curriedValidate( (str: string) => str.length >= 8)('Minimum 8 characters required');
// Use validatorsvalidateEmail('user@example.com'); // { valid: true }validateEmail('invalid'); // { valid: false, message: '...' }
validateLength('password123'); // { valid: true }validateLength('short'); // { valid: false, message: '...' }Configuration
Section titled “Configuration”interface LogConfig { level: 'debug' | 'info' | 'warn' | 'error'; prefix?: string;}
function log(config: LogConfig, message: string): void { const prefix = config.prefix ? `[${config.prefix}]` : ''; console[config.level](`${prefix} ${message}`);}
// Create specialized loggersconst createLogger = partial(log, { level: 'info', prefix: 'APP' });const debugLogger = partial(log, { level: 'debug', prefix: 'DEBUG' });const errorLogger = partial(log, { level: 'error', prefix: 'ERROR' });
createLogger('Application started');debugLogger('Debug information');errorLogger('Something went wrong');Advanced Patterns
Section titled “Advanced Patterns”Auto-Currying
Section titled “Auto-Currying”function autoCurry<T extends any[], R>( fn: (...args: T) => R, ...providedArgs: any[]): any { return providedArgs.length >= fn.length ? fn(...(providedArgs as T)) : (...args: any[]) => autoCurry(fn, ...providedArgs, ...args);}
// Usageconst sum = (a: number, b: number, c: number) => a + b + c;const curriedSum = autoCurry(sum);
curriedSum(1, 2, 3); // 6curriedSum(1)(2)(3); // 6curriedSum(1, 2)(3); // 6curriedSum(1)(2, 3); // 6Placeholder Support
Section titled “Placeholder Support”const _ = Symbol('placeholder');
function curry2WithPlaceholder<A, B, C>( fn: (a: A, b: B) => C) { return (a: A | typeof _, b?: B) => { if (a === _ && b !== undefined) { return (realA: A) => fn(realA, b); } if (b === undefined) { return (realB: B) => fn(a as A, realB); } return fn(a as A, b); };}
// Usageconst subtract = (a: number, b: number) => a - b;const curriedSubtract = curry2WithPlaceholder(subtract);
const subtract5 = curriedSubtract(_, 5);subtract5(10); // 5
const subtractFrom10 = curriedSubtract(10);subtractFrom10(3); // 7Memoization with Currying
Section titled “Memoization with Currying”function memoize<A, B>(fn: (a: A) => B): (a: A) => B { const cache = new Map<A, B>();
return (a: A): B => { if (cache.has(a)) { return cache.get(a)!; }
const result = fn(a); cache.set(a, result); return result; };}
// Combine with currying for efficient reuseconst expensiveCalculation = memoize( curry((factor: number, value: number) => { // Expensive computation return factor * value * Math.random(); }));
const calculate5x = expensiveCalculation(5);calculate5x(10); // Cached for factor=5, value=10calculate5x(10); // Returns cached resultCurrying vs Partial Application
Section titled “Currying vs Partial Application”Key Differences
Section titled “Key Differences”// Currying - transforms function arityconst curriedAdd = curry((a: number, b: number) => a + b);curriedAdd(2)(3); // Must provide one arg at a time
// Partial Application - fixes some argumentsconst add2 = partial((a: number, b: number) => a + b, 2);add2(3); // Provide remaining arguments at onceWhen to Use Each
Section titled “When to Use Each”Use Currying When:
- Building function pipelines
- Creating specialized function factories
- Working with functional libraries (Ramda, fp-ts)
- Need maximum flexibility in argument application
Use Partial Application When:
- Have fixed configuration or context
- Creating callback functions
- Simplifying function signatures
- Need to bind some arguments but not all
Best Practices
Section titled “Best Practices”✅ Use currying for function composition ✅ Create specialized functions with partial application ✅ Keep curried functions pure ✅ Use descriptive names for specialized functions ✅ Curry functions that are frequently reused ✅ Document curried function signatures ✅ Use with higher-order functions
Don’ts
Section titled “Don’ts”❌ Over-curry simple functions ❌ Curry functions with many arguments (>3) ❌ Use currying for one-off functions ❌ Mix currying with mutable state ❌ Create confusing intermediate functions ❌ Forget about performance implications ❌ Sacrifice readability for currying