Currying - Next Level Coding
Die Kunst des Programmierens ist ein Eisberg, der sehr weit unter die Wasseroberfläche reicht. Ich erinnere mich daran, dass ich schon vor Jahren dachte, ich hätte die wichtigsten Techniken des Programmierens gemeistert und könne keine großen Überraschungen mehr erleben. Wie sehr ich mich getäuscht hatte.
Eine Technik hat es mir besonders angetan: das Currying. Wer dies nicht beherrscht, dem fehlt ein essentielles Werzeug in seinem Werkzeugkoffer.

Was ist Currying?
Currying beschreibt die partielle Anwendung von Funktionen. Genauer gesagt werden Funktionen mit mehreren Parametern in eine Reihe von Funktionen mit je nur einem Argument umgewandelt. Auf diese Weise können einzelne Parameter einer Funktionen zu einem Zeitpunkt, die restlichen Parameter zu einem späteren Zeitpunkt gesetzt werden. In der Praxis nutzt man beim Currying Funktionen, welche als Rückgabewert wiederum neue Funktionen haben. Eine der Sprachen, in denen Currying häufig eingesetzt wird, ist JavaScript.
Schauen wir uns folgendes Problem an:
function hello(name) {
console.log("Hallo " + name);
}
function compliment(name) {
console.log("Gut siehst du aus, " + name);
}
function bye(name) {
console.log("Auf Wiedersehen, " + name);
}Für mich sieht das sehr WET aus. Früher hätte ich das vielleicht so gelöst:
function print(msg, name) {
console.log(msg + name);
}Das kann allerdings auch schnell WET werden.
const HELLO = "Hallo ";
print(HELLO, "Tom");
print(HELLO, "Sebastian");
print(HELLO, "Albert");
print(HELLO, "Fritz");
print(HELLO, "Georg");An sich sieht das schon ganz sauber aus, nur der erste Parameter wiederholt sich oft. Um das zu vermeiden, können wir nun Currying verwenden:
function createMsg(msg) {
return function(name) {
console.log(msg + name);
}
}Hier haben wir die Funktion print mit 2 Parametern aufgeteilt, sodass wir zwei Funktionen haben, die jeweils nur einen Parameter annehmen. Nun können wir den ersten Parameter einzeln anwenden und die erzeugte Funktion wiederholt verwenden.
const hello = createMsg("Hallo ");
hello("Tom");
hello("Sebastian");
hello("Albert");
hello("Fritz");
hello("Georg");Praxisbeispiel
Erst neulich ärgerte ich mich, weil ich zwei nahezu identische Funktionen exportieren wollte, die sich einzig an einer Stelle unterschieden. Ich hatte eine Funktion geschrieben, die bei einem Zugriff auf einen NodeJS Server überprüft, ob der Nutzer einen gültigen JSON Webtoken hat. Das ganze hatte ich als Middleware entworfen, um so bestimmte API-Zugriffe zu schützen. Nun wollte ich gerne die gleiche Funktion nutzen, um manche Seiten zu schützen. Der einzige Unterschied sollte sein, dass in diesem Fall kein Status 403 im Falle eines Fehlers gesendet werden, sondern der Nutzer stattdessen zur Loginseite weitergeleitet werden sollte. Mittels Currying konnte ich das Problem schnell lösen, ohne einen Codeblock kopieren zu müssen.
In Zeile 24 und 25 wird die Unterscheidung zwischen Weiterleitung zum Login und Senden eines Fehlercodes gemacht. Der Rest des Codes ist für das Verständnis von Currying nicht notwendig, allerdings zum Zwecke der Vollständigkeit dargestellt.
|
|
Wenn man es genau nimmt, müsste für Currying auch die zweite Funktion aufgebrochen werden, sodass sie nur ein Argument nimmt und so weiter, aber das war in diesem Fall weder nötig noch möglich.
Funktionale Programmierung
Für Objekt-orientierte Programmierer mag Currying ungewöhnlich sein, in der funktionalen Programmierung hingegen ist es derart selbstverständlich, dass in F# beispielsweise jede Funktion schon beim Erstellen gecurryied wird.
let add x y = x + y // Funktion, die zwei Zahlen addiert
let five = add 2 3 // = 5
let addFour = add 4 // Neue Funktion, die immer 4 addiert
let six = addFour 2 // = 6Wieso das funktioniert wird ersichtlich, wenn wir uns die automatisch generierten Typen der Funktionen in F# ansehen:
let add x y = x + y // int -> int -> int
let five = add 2 3 // int
let addFour = add 4 // int -> int
let six = addFour 2 // intEine Funktion mit 2 Parametern ist in Wirklichkeit eine Funktion mit nur einem Parameter, die eine Funktion zurück gibt, die den zweiten Parameter annimmt und schließlich den finalen Wert zurück gibt.
Das ist sehr praktisch. In folgendem Beispiel konnte ich davon Gebrauch machen. Ich erledigte im Rahmen meines Studiums eine Aufgabe auf exercism.org. Die Aufgabe lautete: “Finde die Summe aller eindeutigen Vielfachen bestimmter Zahlen bis zu dieser Zahl, aber nicht einschließlich dieser Zahl.”
Mit einfachen Worten: Man bekommt eine Liste von Zahlen, deren Vielfache addiert werden sollen. Die Vielfache sollen allerdings nicht höher als eine weitere angegebene Zahl werden. Beispiel: Man bekommt die Zahlen 2 und 5 und das Maximum ist 7, dann sollen die Zahlen 2, 4, 5 und 6 addiert werden. Das Ergebnis wäre 17.
Ich schrieb die Funktion multiples, die die Vielfachen einer Zahl unter einem Maximum berechnet. Das Maximum müsste ich normalerweise jedes Mal mit angeben. Da in F# Currying der Standard ist, habe ich stattdessen eine neue Funktion multiplesUnderBound erstellt, die ich 3 Zeilen tiefer in einem List.map verwenden kann. Auf diese Weise ist der Code angenehm DRY und prägnant.
module SumOfMultiples
let multiples upperBound number = [ 0 .. number .. upperBound-1 ]
let notZero n = n <> 0
let sum (numbers: int list) (upperBound: int): int =
let multiplesUnderBound = multiples upperBound
numbers
|> List.filter notZero
|> List.map multiplesUnderBound
|> List.map Set.ofList
|> Set.unionMany
|> Seq.sumAbschließende Gedanken
Grundvorraussetzung für Currying ist, dass in der gewählten Sprache während der Laufzeit Funktionen definiert werden können. Das ist beispielsweise in JavaScript und Python der Fall.
Andererseits gibt es auch in Sprachen wie C Überlegungen, wie Currying mit einigen Tricks angewandt werden kann. Weitere Informationen dazu kann man beispielsweise in Damis Paper More Functional Reusability in C / C++ / Objective-c with Curried Functions: working paper lesen.