zurück zum Artikel

Konsole & …: Currying

the next big thing

"Konsole & Kontext" ist eine gemeinsame Serie von Golo Roden und Michael Wiedeking, in der sich die beiden regelmäßig mit Konzepten der (funktionalen) Programmierung beschäftigen. Während "Konsole & …" die praktische Anwendung dieser Konzepte in JavaScript und Node.js zeigt, erläutert "... & Kontext [1]" deren jeweiligen eher theoretischen Hintergrund.

Die vergangene Folge hat Funktionsabschlüsse [2] als eine spezielle Art von Funktionen vorgestellt, die sich den ursprünglichen zum Zeitpunkt ihrer Erzeugung gültigen Kontext merken und auf diesen später wieder zugreifen können. Außerdem wurde gezeigt, wie sich auf diesem Weg private Variablen in JavaScript simulieren lassen.

Funktionsabschlüsse legen aber auch für andere Szenarien die Grundlage, unter anderem für das sogenannte Currying. Dabei handelt es sich um ein Verfahren, um eine Funktion mit n Parametern in eine neue Funktion mit nur einem einzigen Parameter umzuwandeln. Die neu erzeugte Funktion gibt eine weitere Funktion mit n - 1 Parametern zurück.

Von n nach n – 1

Als Beispiel lässt sich erneut eine Funktion namens add zum Addieren zweier Zahlen heranziehen:

var add = function (first, second) {
return first + second;
};

Um Currying auf diese Funktion anwenden zu können, benötigt man zunächst eine entsprechende curry-Funktion, die sich leicht wie folgt definieren lässt:

var curry = function (fn) {
return function (first) {
return function () {
var args = Array.prototype.slice.call(arguments);
args.unshift(first);

return fn.apply(null, args);
};
};
};

Ruft man diese Funktion nun auf und übergibt die add-Funktion als Parameter, erhält man als Ergebnis eine Funktion, die nur noch einen Parameter erwartet. Ruft man diese wiederum auf, erhält man den "Rest" der add-Funktion, deren erster Parameter vorbelegt ist:

var add5 = curry(add)(5);
console.log(add5(7)); // => 12

Von n nach 1, 1, 1, …

Interessant ist, dass man dieses Verfahren auch wiederholt anwenden kann, um eine Funktion mit mehr als zwei Parametern in eine Reihe von Funktionen mit jeweils einem Parameter zu zerlegen:

var add = function (first, second, third) {
return first + second + third;
};

var add2 = curry(add)(2),
add5 = curry(add2)(3);
console.log(add5(7)); // => 12

Prinzipiell lässt sich auf diesem Weg jede Funktion mit n Parametern in n Funktionen mit jeweils einem Parameter zerlegen, wobei beide gleichwertig sind. Es ist also gleich, ob man eine Funktion von vornherein mit allen erforderlichen Parametern aufruft oder dies nach und nach erledigt.

In JavaScript ist Currying zugegebenermaßen nur von begrenztem Nutzen – wesentlich sinnvoller ist der Einsatz in Sprachen, die Funktionen nur einen einzigen Parameter zugestehen, wie Haskell.

Partielle Funktionsauswertung

Ein Konzept, das auch in JavaScript ausgesprochen hilfreich ist und das gerne mit dem Currying verwechselt wird, ist die partielle Funktionsauswertung: Dabei handelt es sich um den tatsächlichen Aufruf einer Funktion, wobei allerdings auf die Angabe einiger Parameter verzichtet wird.

Als Beispiel hierfür dient die reduce-Funktion, die im Kontext von Funktionen höherer Ordnung [3] bereits beschrieben wurde und unter anderem zur Definition einer sum- und product-Funktion dienen kann:

var reduce = function (init, combine, data) {
var result = init;
for (var i = 0; i < data.length; i++) {
result = combine(result, data[i]);
}
return result;
};

Weist die reduce-Funktion diese Form auf, kann man auf einfachem Weg die beiden genannten arithmetischen Funktionen definieren, indem man eine zusätzliche Funktion namens partiallyApply definiert:

var partiallyApply = function (fn) {
var args = Array.prototype.slice.call(arguments, 1);

return function () {
args = args.concat(Array.prototype.slice.call(arguments));
return fn.apply(null, args);
};
};

var sum = partiallyApply(reduce, 0, function (first, second) {
return first + second;
});

var product = partiallyApply(reduce, 1, function (first, second) {
return first * second;
});

Diese kann man anschließend aufrufen und ihnen nur noch den fehlenden letzten Parameter, das data-Array, übergeben:

var data = [ 1, 2, 3, 4, 5 ];

console.log(sum(data)); // => 15
console.log(product(data)); // => 120

Wie man sieht, sind Currying und die partielle Funktionsauswertung eng miteinander verwandt, allerdings unterscheiden sie sich voneinander: Während es beim Currying darum geht, eine Funktion mit n Parametern auf n Funktionen mit jeweils einem einzigen Parameter zu reduzieren, ermöglicht die partielle Funktionsauswertung das Fixieren von einem oder mehreren Parametern einer Funktion.

Zumindest in JavaScript ist die partielle Funktionsauswertung in der Praxis weitaus hilfreicher und daher auch stärker verbreitet. Trotzdem schadet es nicht, auch das Konzept des Currying zu kennen.

tl;dr: Currying bezeichnet in der funktionalen Programmierung ein Verfahren, um eine Funktion mit n Parametern auf n Funktionen mit jeweils einem einzigen Parameter zu reduzieren. Die partielle Funktionsauswertung ermöglicht das Fixieren von einem oder mehreren Parametern, so dass eine Funktion mit weniger Parametern als die ursprüngliche Funktion entsteht.


URL dieses Artikels:
http://www.heise.de/-2049605

Links in diesem Artikel:
[1] https://www.heise.de/developer/artikel/Kontext-Currying-2050997.html
[2] https://www.heise.de/developer/artikel/Konsole-Funktionsabschluesse-1980456.html
[3] https://www.heise.de/developer/artikel/Konsole-Funktionen-hoeherer-Ordnung-1958717.html