Was ist generische Programmierung?
Aus dem Wunsch, eine kombinatorische Explosion redundanter Implementierungen von Algorithmen zu vermeiden, ergibt sich meine Definition von generischer Programmierung (es gibt auch andere):
Das Wort sinnvoll ist hier mit Bedacht gewählt — es beinhaltet etwa, dass die Implementierung die optimale Performanz für die unterstützten Daten liefert. Ein binäre Suche kann z.B. auf einem (sortierten) Array wesentlich effizienter durchgeführt werden als auf einer (sortierten) Liste, da wir in konstanter Zeit auf jede Stelle des Array zugreifen können. Allerdings lässt sich in diesem Fall der Unterschied kapseln: Das Springen an eine beliebige Stelle der Sequenz wird in einen Unteralgorithmus gesteckt, dessen Implementierung je nach Repräsentation entweder direkt springt oder sich Eintrag für Eintrag vorwärts hangelt. Mit anderen Worten: Die Implementierung wird auf die Daten-Repräsentation spezialisiert.
Dieses Wechselspiel zwischen Generalisierung und Spezialisierung ist ganz charakteristisch für generische Programmierung. Ohne die Möglichkeit zur Spezialisierung eines algorithmischen Bausteins (Springen bzw. Bewegen in einer Sequenz) und dessen automatischer Auswahl aufgrund des Datentyps könnten wir die binäre Suche nur eingeschränkt sinnvoll generisch implementieren. Wir hätten entweder eine allgemeine Implementierung, die auf Arrays ineffizient ist, oder wir müssten zwei komplett verschiedene Implementierungen schreiben (was uns wieder von unserem Ziel entfernt). Und jeder Code, der die binäre Suche benutzt, hätte ohne automatische Selektion der richtigen Spezialisierung wiederum das Problem, diese Unterscheidung selbst zu machen. Spezialisierung ist daher eigentlich fast immer am Werk, wenn eine generische Implementierung benutzt wird. Auch in unserem Beispiel mit der Summe wird letztlich bei der Addition auf die konkreten Datentypen spezialisiert: Der Compiler erzeugt unterschiedlichen Code, je nachdem ob wir einen int-Array oder einen double-Array hineingeben. Es gilt also:
Das Erzeugen von spezialisiertem, optimalen Code aus einer einheitlichen Quelle ist meiner Meinung nach ein gutes Unterscheidungsmerkmal, um Ansätze zur generischen Programmierung voneinander abzugrenzen. In Java etwa wird aus einer generischen Definition immer nur ein und derselbe Byte-Code erzeugt; durch die type erasure gehen jegliche argument-spezifische Typ-Informationen verloren. Dadurch sind Java Generics für primitive Typen nicht nutzbar und für performanz-kritische technische und wissenschaftliche Software unattraktiv. Hingegen wird in C++, wie wir gesehen haben, spezialisierter Code erzeugt (was wiederum zu Duplikationen im generierten Objektcode führen kann). Java Generics sind - trotz gewisser syntaktischer Ähnlichkeiten - etwas vollkommen anderes als C++ Templates!