Currying
Currying is the technique of transforming a function that accepts multiple arguments into a chain of functions that each accept exactly one argument, so that calling the first function with the first argument returns a new function waiting for the second argument, and so on until every argument has been supplied and a final result is produced — a two-argument function becomes a one-argument function that, given the first argument, returns another one-argument function that, given the second, produces the result. This is distinct from partial application, which means fixing some subset of a function's arguments to produce a new function over the rest, since partial application does not require restructuring a function into a one-argument-at-a-time chain and can be done directly on an ordinary multi-argument function. Languages such as Haskell curry every function by default, with no special syntax needed to call a curried function one argument at a time; Java has no built-in support for currying, so a curried function must be constructed by hand as a chain of nested functional interfaces, most often Function<A, Function<B, R>> for two arguments, nested one layer deeper for each additional argument. This entry covers the precise definition of currying and how it differs from partial application, how to build curried functions in Java using nested Function types and a reusable curry helper, and the practical situations where currying earns its verbosity in Java versus situations where a simpler approach reads better.
What Currying Is, and How It Differs from Partial Application
// ── The type-level transformation that currying performs ───────────────
// Uncurried: takes both arguments at once
BiFunction<Integer, Integer, Integer> addUncurried = (a, b) -> a + b;
// Curried: takes ONE argument, returns a function awaiting the next one
Function<Integer, Function<Integer, Integer>> addCurried =
a -> b -> a + b;
int viaUncurried = addUncurried.apply(3, 4); // 7, both args at once
int viaCurried = addCurried.apply(3).apply(4); // 7, one arg at a time
// ── Partial application WITHOUT currying — fixing one arg directly ─────
static Function<Integer, Integer> bindFirstArg(BiFunction<Integer, Integer, Integer> fn, int fixedA) {
return b -> fn.apply(fixedA, b); // no curried chain built — just one wrapper
}
Function<Integer, Integer> addThree = bindFirstArg(addUncurried, 3);
System.out.println(addThree.apply(4)); // 7 — partial application, no currying involved
// ── Calling a curried function partway IS itself partial application ───
Function<Integer, Integer> alsoAddThree = addCurried.apply(3); // stop after 1 arg
System.out.println(alsoAddThree.apply(4)); // 7 — same result, reached via curryingImplementing Curried Functions in Java with Nested Functional Interfaces
// ── Manually writing a curried function ─────────────────────────────────
Function<Integer, Function<Integer, Integer>> multiplyCurried =
a -> b -> a * b;
System.out.println(multiplyCurried.apply(6).apply(7)); // 42
// ── Generalizing to three arguments: one more layer of nesting ─────────
Function<Integer, Function<Integer, Function<Integer, Integer>>> volumeCurried =
l -> w -> h -> l * w * h;
System.out.println(volumeCurried.apply(2).apply(3).apply(4)); // 24
// ── A reusable curry() helper for two-argument functions ───────────────
static <A, B, R> Function<A, Function<B, R>> curry(BiFunction<A, B, R> fn) {
return a -> b -> fn.apply(a, b);
}
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
Function<String, Function<Integer, String>> repeatCurried = curry(repeat);
System.out.println(repeatCurried.apply("ab").apply(3)); // "ababab"
// ── A TriFunction interface (not provided by the JDK) and its curry() ──
@FunctionalInterface
interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
}
static <A, B, C, R> Function<A, Function<B, Function<C, R>>> curry(TriFunction<A, B, C, R> fn) {
return a -> b -> c -> fn.apply(a, b, c);
}
TriFunction<Integer, Integer, Integer, Integer> volume = (l, w, h) -> l * w * h;
Function<Integer, Function<Integer, Function<Integer, Integer>>> volumeCurried2 = curry(volume);
System.out.println(volumeCurried2.apply(2).apply(3).apply(4)); // 24Practical Uses and the Verbosity Trade-off
// ── A curried discount calculator, configured once, reused many times ──
Function<Double, Function<Integer, Integer>> discountedPriceCurried =
rate -> price -> (int) Math.round(price * (1 - rate));
Function<Integer, Integer> twentyPercentOff = discountedPriceCurried.apply(0.20);
Function<Integer, Integer> tenPercentOff = discountedPriceCurried.apply(0.10);
List<Integer> prices = List.of(100, 250, 80, 60);
List<Integer> salePrices = prices.stream().map(twentyPercentOff).toList();
// twentyPercentOff is reused across every price with NO risk of the rate
// being mixed up between calls, because the rate is already baked in.
// ── The same logic, three ways — illustrating the verbosity trade-off ──
// (a) Plain method — clearest for a single, one-off call:
static int discountedPricePlain(int price, double rate) {
return (int) Math.round(price * (1 - rate));
}
discountedPricePlain(100, 0.20); // simplest call
// (b) One-step partial application — good when the rate is fixed
// once and reused, but full currying isn't otherwise needed:
static Function<Integer, Integer> withRate(double rate) {
return price -> discountedPricePlain(price, rate);
}
Function<Integer, Integer> off20 = withRate(0.20);
off20.apply(100);
// (c) Full currying — most ceremony, but composes naturally with other
// curried/higher-order-function pipeline code that expects this shape:
Function<Double, Function<Integer, Integer>> curried = rate -> price -> discountedPricePlain(price, rate);
curried.apply(0.20).apply(100);