Aber als Currying bezeichnet man allgemein die Übersetzung einer Funktion mit mehreren Parametern in eine Reihe von Funktionen, die jeweils nur einen Parameter besitzen.
Genau, das verdeutlicht man sich am besten, indem man sich die Typen solcher Funktionen anschaut. Dafür muss man sich den Definitions- und Bildbereich genauer ansehen. Die zweistellige Addition auf natürlichen Zahlen , nimmt zwei natürliche Zahlen entgegen und berechnet als Ergebnis wiederum eine natürliche Zahl , mathematisch aufgeschrieben: add: (ℕ × ℕ) ⟶ ℕ
Alternativ kann man die Funktion aber auch als Funktion höherer Ordnung auffassen, die zunächst eine natürliche Zahl entgegen nimmt und dann eine Funktion zurück gibt, die eine weitere Zahl entgegen nimmt und als finales Ergebnis wieder eine natürliche Zahl liefert. Der Definitionsbereich dieser Funktion wären also die natürlichen Zahlen, der Bildbereich wären einstellige Funktionen von den natürlichen Zahlen in die natürlichen Zahlen: add: ℕ ⟶ (ℕ ⟶ ℕ)
In JavaScript lassen sich beide Varianten implementieren:
// add1: (ℕ × ℕ) ⟶ ℕ
add1 = (a,b) => a + b
// add2: ℕ ⟶ (ℕ ⟶ ℕ)
add2 = a => (b => a + b)
Hat man add1
gegeben, kann man damit auch add2
implementieren ohne +
direkt zu verwenden:
add2 = a => (b => add1(a,b))
Dieser Vorgang nennt sich Currying. Die andere Richtung ist natürlich auch möglich, sie bezeichnet man als Uncurrying:
add1 = (a,b) => add2(a)(b)
Currying macht man in der Regel nicht von Hand, sondern lässt es von einer Funktion erledigen. Dazu ist es zunächst wieder sinnvoll sich den Typen der curry
-Funktion vorzustellen: Im Definitionsbereich nimmt curry
zweistellige Funktionen auf den natürlichen Zahlen entgegen. Im Bildbereich liefert curry
Funktionen höherer Ordnung über den natürlichen Zahlen. Ingesamt hat man also:
curry: ((ℕ × ℕ) ⟶ ℕ) ⟶ (ℕ ⟶ (ℕ ⟶ ℕ))
Für uncurry
müssen wir nur die Typen des Definitions- und Bildbereiches austauschen:
uncurry: (ℕ ⟶ (ℕ ⟶ ℕ)) ⟶ ((ℕ × ℕ) ⟶ ℕ)
Die beiden Funktionen lassen sich dann wie folgt implementieren:
// curry: ((ℕ × ℕ) ⟶ ℕ) ⟶ (ℕ ⟶ (ℕ ⟶ ℕ))
curry = f => (a => (b => f(a,b)))
// uncurry: (ℕ ⟶ (ℕ ⟶ ℕ)) ⟶ ((ℕ × ℕ) ⟶ ℕ)
uncurry = f => ((a,b) => f(a)(b))
Zurück zum Additions-Beispiel könnte man nun die Funktion add2
aus add1
nur mit der curry
-Funktion erzeugen:
add2 = curry(add1)
Und der Vollständigkeit halber sei auch noch die Umkehrrichtung gezeigt:
add1 = uncurry(add2)
Man sieht schnell, dass die curry
- und uncurry
-Funktionen eigentlich nicht auf natürliche Zahlen beschränkt sind. Wenn a
, b
und c
für beliebige Typen stehen, können wir den Funktionen die beiden folgenden allgemeineren Typen zuordnen.
curry: ((a × b) ⟶ c) ⟶ (a ⟶ (b ⟶ c))
uncurry: (a ⟶ (b ⟶ c)) ⟶ ((a × b) ⟶ c)
Ebenso kann man die Funktionen so verallgemeinern, dass sie auf beliebigen n-Tupeln operieren. Dafür müsste aber die Implementierung geändert werden.
Ein klassisches Beispiel hierfür sieht in etwa so aus:
function add (a, b) { if (b === undefined) { return function (b) { return a + b; } } return a + b; }
Das Beispiel von @Orlok zeigt sogenanntes Autocurrying. Das ist eine Sonderform, die den Unterschied zwischen der zweistelligen Addition und der Addition höherer Ordnung vollständig weg abstrahiert. Man kann diese Funktion wie add1
und wie add2
aufrufen. Diese Abstraktion kann man nur in dynamisch typisierten Sprachen realisieren, weil man ihr schlicht keinen statischen Typen zuordnen kann.