Programmiersprachen-Design, Teil 1: Nicht alle Gleichheiten sind gleich
Konzepte, Theorien, Sicht- und Vorgehensweisen haben ein Beharrungsvermögen. Einmal verinnerlicht, werden sie nicht mehr hinterfragt, oft sogar ins Herz geschlossen. Dieser Artikel stellt eine verbreitete Sichtweise der Programmierung in Frage: die Trennung von Gleichheit und Identität.
- Beate Ritterbach
Die Trennung von Gleichheit und Identität wird in Programmierkursen vermittelt, und indirekt beeinflusst sie das Design vieler Programmiersprachen. Hier werden Gründe dargelegt, warum diese Einteilung nicht die einzig mögliche und nicht die vorteilhafteste ist.
Bestandsaufnahme: Gleichheit(en) in Programmiersprachen
Gleichheit, ein grundlegendes und scheinbar einfaches Konzept der Programmierung, ist in vielen Sprachen kompliziert, verworren und fehleranfällig. Es beginnt damit, dass fast alle objektorientierten Sprachen zwei Vergleiche kennen: Gleichheit und Identität. Teils tragen sie andere Namen: Erstere wird auch als Inhaltsgleichheit, Letztere als Referenzgleichheit bezeichnet. Je nach Sprache gibt es nicht nur verschiedene Terminologien, sondern auch unterschiedliche Operatoren oder Methoden: Java unterscheidet "==" und "equals", C# kennt "==" und "Equals", Scala "==" und "eq" und Python "==" und "is". In Kotlin, Ceylon und Swift gibt es die beiden Operatoren "===" und "==". Siehe dazu Tabelle 1.
| Sprache | Vergleiche | Vergleiche |
| Java | == | equals |
| C# | == | Equals |
| Scala | eq | == |
| Python | is | == |
| Kotlin | === | == |
| Ceylon | === | == |
| Swift | === | == |
Manche Sprachen kennen sogar mehr als zwei Gleichheiten. Zum Beispiel gibt es in C# zusätzlich noch in der Wurzelklasse Object die beiden statischen Methoden Equals und ReferenceEquals.
Das Bonmot, gemäß dem viel auch viel hilft, stimmt hier nicht. Zwei oder mehr Vergleiche machen die Programmierung fehleranfällig. Beispielsweise bergen sie die Gefahr, versehentlich den "falschen" Vergleich zu benutzen. Bekannt, fast schon berüchtigt, ist die Stolperfalle, Strings in Java mit "==" zu vergleichen – stattdessen müssen Entwickler equals verwenden. Programmieranfänger müssen diese Regel mühsam erlernen; erfahrene Entwickler tappen immer wieder in jene Falle. Es gibt IT-Abteilungsleiter, die mit der Fragestellung Bewerbern auf den Zahn fühlen.
Einige nachfolgende Sprachen haben diese Falle beseitigt. In C# beispielsweise gibt es für den Operator "==" in der Klasse String eine Sonderregelung. In Scala ruft der Operator "==" seinerseits die Methode equals auf und ist damit faktisch dazu synonym. Darum funktioniert in C# und Scala der Vergleich von Strings mittels "==" erwartungsgemäß. Die anderen Schwierigkeiten aber bleiben bestehen: Es gibt nicht nur eine Gleichheit, sondern mehrere. Es ist nicht immer klar, welche Gleichheit wann zu verwenden ist, welche Bedeutung sie hat und ob jede der Gleichheiten überhaupt für alle Typen dieselbe Bedeutung hat.
Dass der Unterschied zwischen den Gleichheiten eher diffus ist, dass sie womöglich keine entgegengesetzten, einander ausschließenden Konzepte darstellen, zeigt sich bereits daran, dass oft die eine als Default-Implementierung für die andere verwendet wird. Beispielsweise besteht die von equals in java.lang.Object aus einem Aufruf von "==". Deshalb verhalten sich hier equals und "==" genau gleich – abgesehen davon, dass es bei equals zu NullPointerExceptions kommen kann.
Die Auswirkungen mehrerer Gleichheiten reichen noch weiter: Gleichheit liegt etlichen Sammlungen (Collections) zugrunde. Zum Beispiel wird sie gebraucht, um festzustellen, ob ein Element in einer Menge (Set) enthalten ist oder nicht. Auf die Weise bestimmt Gleichheit das gesamte Verhalten von Mengen: beim Suchen und EinfĂĽgen von Elementen, bei der Bildung von Schnitt- und Vereinigungsmengen et cetera.
Ähnliches gilt für Dictionaries, bei denen es darum geht, ob der Schlüssel eines Eintrags bereits vorhanden ist. Wenn es zwei Gleichheiten gibt, dann müssen Sammlungen wie Set oder Dictionary in doppelter Ausführung vorhanden sein. Tatsächlich kennen die Python-Bibliotheken neben einem "normalen" Set (das "==" nutzt) ein IdentitySet (basierend auf is); und in den Smalltalk-Klassen gibt es neben einem "normalen" Dictionary ein IdentityDictionary. Konsequent umgesetzt führen die zwei Gleichheiten zu einer Verdopplung solcher Collection-Klassen.
Es ist schwer absehbar, wo und wie sich zwei Gleichheiten noch auswirken. Oder um es mit Scala-Erfinder Martin Odersky zu sagen: "equality is at the basis of many other things".