Boron schrieb:
Was zur Hölle sind das für Dinger und für was braucht man die?
Um den Nutzen von Lambda Ausdrücken zu verstehen, muss man den Unterschied zwischen funktionaler und imperativer Programmierung verstehen. Lambda Ausdrücke ermöglichen nämlich eine funktionale Problemlösung. Normale Programmiersprachen, wie C, C++, C# und Java unterstützen in erster Linie die imperative Programmierung.
Das heißt, ich beschreibe detailliert was mein Code macht. Beim funktionalen Ansatz wird das Problem als Satz von auszuführenden Funktionen formuliert, ohne das die Reihenfolge der Berechnungen angegeben werden muss. Das Programm beschreibt lediglich Abhängigkeiten von Daten.
Wir sprechen hier also von Programmierparadigmen.
Nehmen wir das klassische Standardbeispiel. Wir suchen Mitarbeiter, deren Gehalt zwischen zwei Schranken liegt. Wir benötigen dies nur einmal im Programm. In C++ definieren wir dafür eine Funktion.
Code:
class between
{
double low, high;
public:
between(double l, double u) : low(l), high(u) { }
bool operator()(const employee& e)
{
return e.salary() >= low && e.salary() < high;
}
}
und rufen diese im Code wie folgt auf.
Code:
...
double min_salary;
...
std::find_if(employees.begin(), employees.end(), between(min_salary, 1.1 * min_salary));
Die Generierung von derartigen Funktionsobjekten ist aufwendig und fehleranfällig. Hier kommen Lambda Ausdrücke ins Spiel.
Den ganzen Kram kann ich nämlich anonymer in einer Zeile abarbeiten.
Code:
std::find_if(employees.begin(), employees.end(), <&>(const employee& e) (e.salary() >= min_salary && e.salary() < u_limit));
Der wesentliche Punkt ist, das bei der funktionalen Programmierung die Lesbarkeit und Verwaltbarkeit des Codes erhöht wird, weil das Lambda-Kalkül die vollständige Auswertung seperater Teilausdrücke gestattet. Man muss keine Variablen zuweisen. Dadurch gibt es weniger Programmierfehler, z.B. durch einen falsch zugewiesenen Wert. Ich muss mir auch keine Gedanken über den Kontrollfluss machen.
Bei der funktionalen Programmierung werden Zustands- und änderbare Daten vermieden, stattdessen steht die Anwendung von Funktionen im Vordergrund.
Die Fakultät kann ich daher einfach so schreiben.
Code:
Fakultat (x) { Fakultat = Fakultat(x-1) * x }
Das wars. Man sieht, es ist ein rein mathematischer Ansatz des Problems. Keine Variablenreservierung, keine explizite Programmflusskontrolle mit Schleifen, if, else, etc.. Mit dem Lambda Operator (=>) in C# 3.5 kann ich bequem Abbildungen beschreiben, statt explizit Rechenanweisungen zu schreiben.
Code:
List<int> ages = persons.ConvertAll(p => p.Age);
Das gibt mir die Liste mit den Altern von allen Personen, während
Code:
persons.RemoveAll(p => p.Age > 50);
mal schnell alle Personen aus meiner Liste entfernt, die älter als 50 sind. In Java müsste ich mittels foreach durch die Liste iterieren, mit if testen ob die Person älter als 50 ist und sie dann mit remove - ist immerhin Iterator safe - entfernen.
Code:
for(Person p : persons) {
if(p.Age > 50) {
persons.remove(p);
}
}
Noch schmlimmer wird es, wenn ich mit Java beliebig nach Elementen sortieren wollte, Stichwort Comparable implementieren. Dann würde mein Code noch deutlich umfangreicher werden, während ich das in C# 3.5 auch mit einem Einzeiler abhaken kann.
Code:
persons.Sort((first, second) => first.Age.CompareTo(second.Age));
Rekursionen sind in imperativen Programmiersprachen zum Beispiel ein funktionaler Ansatz, der den meisten bekannt sein dürfte.