d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
7.2 KiB
7.2 KiB
Currying Pattern
Problem
You have functions that take multiple arguments, but you often want to fix some arguments and reuse the partially-applied function. Without currying, you end up writing many small wrapper functions manually.
Solution
Currying transforms a function that takes multiple arguments into a chain of functions, each taking a single argument. Partial application is the related concept of fixing some arguments to produce a new function with fewer parameters.
When to Use
- Creating specialized versions of generic functions (e.g.,
add(1)becomesincrement) - Building configurable function pipelines
- Event handlers where some context is known at registration time
- Dependency injection at the function level
When to Avoid
- When it hurts readability (over-currying makes call sites cryptic)
- Performance-sensitive code where closure allocation matters
- Simple functions where direct calls are clearer
Pseudocode
// Currying: f(a, b, c) becomes f(a)(b)(c)
function add(a):
return function(b):
return a + b
increment = add(1)
print(increment(5)) // 6
// Partial application
function greet(greeting, name):
return greeting + ", " + name + "!"
sayHello = partial(greet, "Hello")
print(sayHello("Alice")) // "Hello, Alice!"
Python
from functools import partial
from typing import Callable
# Manual currying
def add(a: int) -> Callable[[int], int]:
def inner(b: int) -> int:
return a + b
return inner
# Generic curry decorator
def curry(fn: Callable) -> Callable:
import inspect
arity = len(inspect.signature(fn).parameters)
def curried(*args):
if len(args) >= arity:
return fn(*args)
return lambda *more: curried(*args, *more)
return curried
@curry
def multiply(a: int, b: int, c: int) -> int:
return a * b * c
def greet(greeting: str, name: str) -> str:
return f"{greeting}, {name}!"
def main() -> None:
# Manual currying
increment = add(1)
add_ten = add(10)
print(f"increment(5) = {increment(5)}")
print(f"add_ten(5) = {add_ten(5)}")
# Curried multiply
double = multiply(2)(1) # 2 * 1 * c
print(f"multiply(2)(3)(4) = {multiply(2)(3)(4)}")
print(f"double = multiply(2)(1), double(7) = {double(7)}")
# functools.partial
say_hello = partial(greet, "Hello")
say_hi = partial(greet, "Hi")
print(f"say_hello('Alice') = {say_hello('Alice')}")
print(f"say_hi('Bob') = {say_hi('Bob')}")
# Practical: configurable formatter
def format_number(prefix: str, decimals: int, value: float) -> str:
return f"{prefix}{value:.{decimals}f}"
format_usd = partial(format_number, "$", 2)
format_eur = partial(format_number, "EUR ", 2)
format_pct = partial(format_number, "", 1)
print(f"\nformat_usd(1234.5) = {format_usd(1234.5)}")
print(f"format_eur(1234.5) = {format_eur(1234.5)}")
print(f"format_pct(99.876) = {format_pct(99.876)}%")
# Pipeline with curried functions
numbers = [1, 2, 3, 4, 5]
add_one = add(1)
result = list(map(add_one, numbers))
print(f"\nmap(add(1), {numbers}) = {result}")
if __name__ == "__main__":
main()
Go
package main
import "fmt"
// Curried add: add(a)(b)
func add(a int) func(int) int {
return func(b int) int {
return a + b
}
}
// Curried multiply: multiply(a)(b)(c)
func multiply(a int) func(int) func(int) int {
return func(b int) func(int) int {
return func(c int) int {
return a * b * c
}
}
}
// Curried greeter
func greet(greeting string) func(string) string {
return func(name string) string {
return fmt.Sprintf("%s, %s!", greeting, name)
}
}
// Generic partial application using closures
func formatNumber(prefix string, decimals int) func(float64) string {
return func(value float64) string {
format := fmt.Sprintf("%s%%.%df", prefix, decimals)
return fmt.Sprintf(format, value)
}
}
func main() {
// Curried add
increment := add(1)
addTen := add(10)
fmt.Printf("increment(5) = %d\n", increment(5))
fmt.Printf("addTen(5) = %d\n", addTen(5))
// Curried multiply
fmt.Printf("multiply(2)(3)(4) = %d\n", multiply(2)(3)(4))
double := multiply(2)(1)
fmt.Printf("double(7) = %d\n", double(7))
// Curried greeter
sayHello := greet("Hello")
sayHi := greet("Hi")
fmt.Printf("sayHello(\"Alice\") = %s\n", sayHello("Alice"))
fmt.Printf("sayHi(\"Bob\") = %s\n", sayHi("Bob"))
// Configurable formatter
formatUSD := formatNumber("$", 2)
formatEUR := formatNumber("EUR ", 2)
formatPct := formatNumber("", 1)
fmt.Printf("\nformatUSD(1234.5) = %s\n", formatUSD(1234.5))
fmt.Printf("formatEUR(1234.5) = %s\n", formatEUR(1234.5))
fmt.Printf("formatPct(99.876) = %s%%\n", formatPct(99.876))
// Pipeline with curried functions
numbers := []int{1, 2, 3, 4, 5}
addOne := add(1)
result := make([]int, len(numbers))
for i, n := range numbers {
result[i] = addOne(n)
}
fmt.Printf("\nmap(add(1), %v) = %v\n", numbers, result)
}
JavaScript
// Curried add using arrow functions
const add = (a) => (b) => a + b;
// Curried multiply
const multiply = (a) => (b) => (c) => a * b * c;
// Curried greeter
const greet = (greeting) => (name) => `${greeting}, ${name}!`;
// Generic curry utility
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
// Configurable formatter (non-curried, then curried)
function formatNumber(prefix, decimals, value) {
return `${prefix}${value.toFixed(decimals)}`;
}
const curriedFormat = curry(formatNumber);
function main() {
// Curried add
const increment = add(1);
const addTen = add(10);
console.log(`increment(5) = ${increment(5)}`);
console.log(`addTen(5) = ${addTen(5)}`);
// Curried multiply
console.log(`multiply(2)(3)(4) = ${multiply(2)(3)(4)}`);
const double = multiply(2)(1);
console.log(`double(7) = ${double(7)}`);
// Curried greeter
const sayHello = greet("Hello");
const sayHi = greet("Hi");
console.log(`sayHello("Alice") = ${sayHello("Alice")}`);
console.log(`sayHi("Bob") = ${sayHi("Bob")}`);
// Generic curry utility
const formatUSD = curriedFormat("$")(2);
const formatEUR = curriedFormat("EUR ")(2);
const formatPct = curriedFormat("")(1);
console.log(`\nformatUSD(1234.5) = ${formatUSD(1234.5)}`);
console.log(`formatEUR(1234.5) = ${formatEUR(1234.5)}`);
console.log(`formatPct(99.876) = ${formatPct(99.876)}%`);
// Pipeline with curried functions
const numbers = [1, 2, 3, 4, 5];
const addOne = add(1);
const result = numbers.map(addOne);
console.log(`\nmap(add(1), [${numbers}]) = [${result}]`);
// Compose curried functions
const pipe =
(...fns) =>
(x) =>
fns.reduce((v, f) => f(v), x);
const transform = pipe(add(1), multiply(2)(3));
console.log(`pipe(add(1), multiply(2)(3))(4) = ${transform(4)}`);
}
main();
Related Patterns
- Partial Application — partial application fixes some arguments of a function; currying is the structural mechanism that makes partial application systematic.
- Higher-Order Functions — curried functions are higher-order functions; currying and HOFs are deeply interrelated.
- Builder — a curried builder applies configuration arguments one at a time, building up an object fluently.