1unitedpower: this

Beitrag lesen

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.