Files
freemo d9e5668cec fix(skills): comprehensive final audit pass for programming-patterns skill
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
2026-04-15 13:22:13 -04:00

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) becomes increment)
  • 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();
  • 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.