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.

JavaScript partial-grundform.js
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 ]
Output
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.

JavaScript partial-helper.js
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?'));
Output
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.

JavaScript curry-manuell.js
// 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));     // 18
Output
6
18

Jede 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:

JavaScript curry-helper.js
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 — gemischt
Output
6
6
6
6

Der 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.

JavaScript curry-pipeline.js
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 ]
Output
[ '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:

AspektCurryingPartial Application
SchichtenEine Funktion pro ArgumentBeliebige Anzahl pro Schritt
Aufruf-Formf(a)(b)(c)partial(f, a)(b, c)
ReihenfolgeStrikt von links nach rechtsBeliebig (mit Placeholder)
Native Unterstützungnein — Helper nötigbind (Helper für Placeholder)
Häufig in JavaScriptBibliotheken (Ramda, lodash/fp)Direkt mit bind
Häufig in Haskell, Elm, OCamlSprach-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.

JavaScript pipe-compose.js
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-Richtung
Output
49
49

pipe 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/compose gewü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.
JavaScript wann-sinnvoll.js
// 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));
Output
[ { 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

/ Weiter

Zurück zu Funktionen

Zur Übersicht