Currying und Partial Application sind zwei verwandte Patterns aus der funktionalen Programmierung. Currying wandelt eine Funktion mit n Argumenten in eine Kette von n einstelligen Funktionen um: f(a, b, c) wird zu f(a)(b)(c). Partial Application belegt nur einige Argumente vor und liefert eine neue Funktion, die die restlichen erwartet — das ist genau, was Function.prototype.bind mit Argumenten macht. Beide Patterns basieren auf Closures und ermöglichen kompakten, wiederverwendbaren Code in Array-Pipelines und Konfigurations-Helpern. Dieser Artikel zeigt die Mechanik, einen einfachen curry-Helper und die Stellen, an denen Currying praktisch ist — und wo nicht.
Partial Application — Argumente vorbelegen
Partial Application heißt: einer Funktion einen Teil ihrer Argumente vorab geben und eine neue Funktion zurückbekommen, die den Rest erwartet.
function multipliziere(a, b) {
return a * b;
}
// Mit bind — die einfachste Form
const verdoppeln = multipliziere.bind(null, 2);
const verdreifachen = multipliziere.bind(null, 3);
console.log(verdoppeln(5)); // 10
console.log(verdreifachen(5)); // 15
console.log([1, 2, 3].map(verdoppeln)); // [ 2, 4, 6 ]10
15
[ 2, 4, 6 ]bind(null, 2) ist Partial Application: der erste Parameter ist auf 2 fixiert, der zweite wird beim Aufruf erwartet.
Eigener partial-Helper
bind ist auf den ersten Slot beschränkt — Argumente werden in der Reihenfolge gebunden, in der sie übergeben werden. Ein eigener partial-Helper kann flexibler sein, z.B. mit einer Placeholder-Konvention.
const _ = Symbol('placeholder');
function partial(fn, ...vorbind) {
return function(...rest) {
const args = vorbind.map(v => v === _ ? rest.shift() : v);
return fn(...args, ...rest);
};
}
function gruss(salut, name, frage) {
return `${salut}, ${name}! ${frage}`;
}
const hi = partial(gruss, 'Hi', _, 'Alles gut?');
console.log(hi('Anna')); // 'Hi, Anna! Alles gut?'
const annaFragt = partial(gruss, _, 'Anna', _);
console.log(annaFragt('Hey', 'Wie war dein Tag?'));Hi, Anna! Alles gut?
Hey, Anna! Wie war dein Tag?Lodash hat einen ähnlichen Helper mit _.partial(fn, _, 'Anna')-Syntax. In Eigenbau ist das in 10 Zeilen machbar.
Currying — n-stellig zu Kette einstelliger
Currying wandelt f(a, b, c) in f(a)(b)(c) um. Jede Stufe gibt eine neue Funktion zurück, die das nächste Argument erwartet.
// Manuell gecurryt
function summeCurry(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(summeCurry(1)(2)(3)); // 6
// Praktisch in Pipelines
const summeMit5 = summeCurry(5);
const summeMit5Und10 = summeMit5(10);
console.log(summeMit5Und10(3)); // 186
18Jede Schicht ist eine Closure über die bisher gebundenen Argumente. Pro Aufrufkette baut sich der vollständige Argument-Satz auf, bis die innerste Funktion das Ergebnis liefert.
curry-Helper für beliebige Funktionen
Eine Helper-Funktion, die jede n-stellige Funktion in eine gecurryte Form bringt:
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...rest) => curried(...args, ...rest);
};
}
function f(a, b, c) { return a + b + c; }
const fc = curry(f);
console.log(fc(1, 2, 3)); // 6 — direkt mit allen Argumenten
console.log(fc(1)(2)(3)); // 6 — vollständig curried
console.log(fc(1, 2)(3)); // 6 — gemischt
console.log(fc(1)(2, 3)); // 6 — gemischt6
6
6
6Der Helper nutzt fn.length, um die erwartete Argument-Anzahl zu bestimmen. Sobald genug Argumente da sind, wird die ursprüngliche Funktion aufgerufen — vorher gibt es eine neue Funktion zurück.
Anwendung in Array-Pipelines
Currying wird oft genutzt, um in map/filter „Vorbelegungen" zu schreiben — kompakter als ein anonymer Callback.
const curry = fn => function curried(...args) {
if (args.length >= fn.length) return fn(...args);
return (...rest) => curried(...args, ...rest);
};
const props = curry((key, obj) => obj[key]);
const users = [
{ name: 'Anna', alter: 30 },
{ name: 'Bob', alter: 25 },
];
// Direkt: users.map(u => u.name)
// Gecurryt:
const namen = users.map(props('name'));
const alter = users.map(props('alter'));
console.log(namen); // [ 'Anna', 'Bob' ]
console.log(alter); // [ 30, 25 ][ 'Anna', 'Bob' ]
[ 30, 25 ]props('name') liefert eine Funktion, die jedes Object durch sich schickt und .name herauszieht. Ramda und andere FP-Libraries haben das vor-implementiert.
Currying vs. Partial Application
Die Unterschiede in einer Tabelle:
| Aspekt | Currying | Partial Application |
|---|---|---|
| Schichten | Eine Funktion pro Argument | Beliebige Anzahl pro Schritt |
| Aufruf-Form | f(a)(b)(c) | partial(f, a)(b, c) |
| Reihenfolge | Strikt von links nach rechts | Beliebig (mit Placeholder) |
| Native Unterstützung | nein — Helper nötig | bind (Helper für Placeholder) |
| Häufig in JavaScript | Bibliotheken (Ramda, lodash/fp) | Direkt mit bind |
| Häufig in Haskell, Elm, OCaml | Sprach-Default (alle Funktionen) | Aus Currying ableitbar |
In JavaScript ist Partial Application via bind der idiomatischere Weg. Currying ist ein FP-Pattern, das in einem JS-Codebase eher ein Stil-Statement ist.
Pipe und Compose — Currying-nahe Pattern
pipe und compose setzen Funktionen zu Pipelines zusammen. In Kombination mit Currying erlauben sie sehr kompakten Funktional-Stil.
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const verdoppeln = x => x * 2;
const plusEins = x => x + 1;
const quadrieren = x => x * x;
const pipeline = pipe(verdoppeln, plusEins, quadrieren);
console.log(pipeline(3)); // ((3*2)+1)^2 = 49
const mathPipeline = compose(quadrieren, plusEins, verdoppeln);
console.log(mathPipeline(3)); // gleiches Ergebnis, gespiegelte Lese-Richtung49
49pipe und compose sind in Ramda, lodash/fp und vielen FP-Libraries vor-implementiert. In Eigenbau passen sie in eine Zeile mit reduce.
Wann ist Currying sinnvoll — wann nicht?
Currying lohnt sich, wenn:
- Mehrere Funktionen mit gleicher Struktur abgeleitet werden (siehe
props('name')-Beispiel). - Eine Pipeline-Form mit
pipe/composegewünscht ist. - Das Team mit FP-Patterns vertraut ist (Ramda, lodash/fp).
Currying ist Overkill, wenn:
- Eine einmalige Funktion gebraucht wird — schlicht ein anonymer Callback ist klarer.
- Funktionen variadic sind (Rest-Parameter) — Currying funktioniert nicht sauber mit
fn.length === 0. - Lesbarkeit über Kompaktheit geht — vier Klammern hintereinander sind nicht für jedes Team idiomatisch.
// Klassisch — klar und direkt
const users = [
{ name: 'Anna', alter: 30 },
{ name: 'Bob', alter: 17 },
];
const erwachsene = users.filter(u => u.alter >= 18);
console.log(erwachsene);
// Mit Curry — kompakter, aber abstrakter
const curry = fn => (...args) => args.length >= fn.length
? fn(...args)
: (...rest) => curry(fn)(...args, ...rest);
const gte = curry((min, val) => val >= min);
const istErwachsen = u => gte(18, u.alter);
console.log(users.filter(istErwachsen));[ { name: 'Anna', alter: 30 } ]
[ { name: 'Anna', alter: 30 } ]Bei der einfachen Filterung ist die direkte Form klarer. Currying glänzt erst, wenn man dieselben Bausteine mehrfach in verschiedenen Pipelines kombiniert.
Besonderheiten
Currying basiert komplett auf Closures
Jede Stufe einer gecurryten Funktion ist eine Closure über die bisher gesammelten Argumente. Ohne Closures gäbe es kein Currying — entsprechend ist das Pattern in Sprachen ohne First-Class-Functions schlicht nicht ausdrückbar.
bind ist die native Partial-Application — kein Library nötig
fn.bind(null, a, b) bindet a und b als erste Argumente vor. Für die meisten Partial-Application-Cases reicht das. Wer Placeholder-Syntax (Argumente an beliebiger Position vorbinden) braucht, kann den eigenen Helper aus diesem Artikel benutzen oder Lodash's _.partial.
Variadic Funktionen brechen das Currying
Wenn fn Rest-Parameter hat, ist fn.length 0 oder gleich der Anzahl der festen Parameter VOR dem Rest. Standard-curry-Helper, die auf fn.length reagieren, brechen daher bei variadic Funktionen. Lösung: explizite Stelligkeit übergeben (curry(fn, 3)).
Ramda und lodash/fp curryen automatisch — andere nicht
Ramda und lodash/fp publizieren ALLE Funktionen als gecurryt — R.map(fn)(arr) oder R.map(fn, arr) beide möglich. Lodash-Standard und natives JavaScript tun das NICHT. Wer beide mischt, muss aufpassen, welche Variante er importiert.
Currying ist nicht 'gut' oder 'schlecht' — sondern Stil-abhängig
In Sprachen wie Haskell oder Elm ist Currying Default und unverzichtbar. In JavaScript ist es eine Stil-Wahl. Manche Codebases (FP-orientiert) nutzen es überall, andere (OO-orientiert) gar nicht. Beides ist legitim — Konsistenz im Team ist wichtiger als die Wahl.
Performance: gecurryte Funktionen sind langsamer als direkte Aufrufe
Jede Curry-Stufe ist ein Function-Call mit eigenem Stack-Frame. In Hot-Loops mit Millionen Iterationen messbar. In normalem Anwendungs-Code unmessbar. Wer Performance-kritisch arbeitet, baut die Pipeline aber ohne Currying — eine direkte Funktion ist schneller.
Auto-Curry mit Proxy ist möglich, aber selten
Mit Proxy und Reflect lässt sich ein curry-Helper bauen, der noch flexibler ist (z.B. mit benamten Argumenten). Praktisch selten gemacht, weil der Performance-Overhead noch höher ist und die Lesbarkeit nicht steigt.
bind hat keinen Placeholder — nur linksbündig vorbinden
fn.bind(null, _, 2) mit einem Platzhalter funktioniert nicht — der Platzhalter wäre nur ein normaler Wert. Für Mid-Argument-Binding braucht man einen eigenen Helper. Im einfachen Fall reicht: einen Wrapper schreiben (const f2 = b => fn(a1, b, c1);).
Weiterführende Ressourcen
Externe Quellen
- Currying – MDN Glossary
- Partial application – Wikipedia
- Ramda Documentation – curry
- lodash – _.partial
- Eric Elliott: Curry and Function Composition