Das „Expression Problem“ ist eine klassische Herausforderung beim Design von Programmiersprachen. In diesem Beitrag untersuchen wir, wie man sich einer Lösung in Go mithilfe von Generics nähern kann, beziehen uns dabei auf die Ideen aus dem Featherweight Go-Paper und beleuchten die Grenzen der aktuellen Go-Implementierung.
Um die Herausforderung zu verstehen, hilft ein Blick darauf, wie das Featherweight Go-Paper sie definiert:
„Das Ziel besteht darin, einen Datentyp durch Fallunterscheidungen zu definieren, sodass man dem Datentyp neue Fälle hinzufügen und neue Funktionen über diesem Datentyp definieren kann, ohne bestehenden Code neu kompilieren zu müssen – und unter Beibehaltung der statischen Typsicherheit.“
Das Paper liefert eine hervorragende Analogie, um sich dies bildlich vorzustellen:
„Ob eine Sprache das Expression Problem lösen kann, ist ein markanter Indikator für ihre Ausdruckskraft. Man kann sich die Fälle als Zeilen und die Funktionen als Spalten einer Tabelle vorstellen. In einer funktionalen Sprache sind die Zeilen fest vorgegeben (Fälle in einer Datentypdeklaration), aber es ist einfach, neue Spalten (Funktionen) hinzuzufügen. In einer objektorientierten Sprache sind die Spalten fest vorgegeben (Methoden in einer Klassendeklaration), aber es ist einfach, neue Zeilen (Unterklassen) hinzuzufügen. Wir möchten erreichen, dass sich sowohl Zeilen als auch Spalten mühelos ergänzen lassen.“
Kurz gesagt: Eine echte Lösung erlaubt es uns, unserem Code problemlos neue Datenvarianten (Zeilen) und neue Operationen bzw. Verhaltensweisen (Spalten) hinzuzufügen, ohne den bestehenden Quellcode anfassen zu müssen.
Die im ursprünglichen Featherweight Go-Entwurf vorgeschlagene Lösung stützt sich auf ein Sprachfeature, das es nicht in die finale Veröffentlichung von Go 1.18 Generics geschafft hat.
Konkret setzt die Ideallösung des Papers voraus, dass man auf den Receiver einer Methode strengere Typ-Constraints anwenden kann, als sie auf dem zugrunde liegenden Struct definiert wurden. Idealerweise würden wir zum Beispiel Folgendes schreiben wollen:
// Hypothetisch (kein gültiges Go):
// Einschränkung direkt am Methoden-Receiver
func (e Plus[T Evaler]) Eval() int
func (e Plus[T Stringer]) String() string
Die tatsächliche Generics-Implementierung von Go verlangt jedoch, dass die Typ-Constraints des Methoden-Receivers exakt mit der Typdefinition des Structs übereinstimmen. Man kann nicht für einzelne Methoden nachträglich neue Constraint-Anforderungen (wie Evaler oder Stringer) hinzufügen.
Da wir Typparameter nicht auf Methodenebene einschränken können, sind wir gezwungen, sie bereits auf Struct-Ebene einzuschränken.
Unten sehen Sie ein angepasstes Codebeispiel, das mit Go 1.18+ Generics einwandfrei funktioniert. Beachten Sie jedoch, dass wir vorab ein kombiniertes Expr-Interface definieren müssen. Da wir dieses Interface jedes Mal ändern müssen, wenn wir eine neue Operation hinzufügen wollen, ist dies keine echte Lösung des Expression Problems, sondern ein praktischer, aus dem Paper abgeleiteter Workaround:
package main
import (
"fmt"
)
// 1. Unseren Basis-Datentyp definieren
type Num struct {
value int
}
// 2. Unsere Operationen (Spalten) definieren
type Evaler interface {
Eval() int
}
type Stringer interface {
String() string
}
// Die Operationen für unseren Basistyp implementieren
func (e Num) Eval() int {
return e.value
}
func (e Num) String() string {
return fmt.Sprintf("%d", e.value)
}
// 3. Der Kompromiss: Wir müssen vorab ein kombiniertes Interface definieren.
// Wenn wir später eine neue Operation hinzufügen wollen, müssen wir dieses Interface bearbeiten,
// was gegen die Grundregel des Expression Problems verstößt.
type Expr interface {
Evaler
Stringer
}
// 4. Einen generischen Datentyp (Zeile) definieren
// Da Go keine Constraints auf Methodenebene erlaubt, müssen wir T hier auf Expr einschränken.
type Plus[T Expr] struct {
left T
right T
}
func (e Plus[T]) Eval() int {
return e.left.Eval() + e.right.Eval()
}
func (e Plus[T]) String() string {
return fmt.Sprintf("(%s+%s)", e.left.String(), e.right.String())
}
// 5. Ausführung
func main() {
var e Expr = Plus[Expr]{left: Num{1}, right: Num{2}}
fmt.Println(e.Eval()) // Ausgabe: 3
fmt.Println(e.String()) // Ausgabe: (1+2)
}
Obwohl Generics in Go enorm leistungsfähig sind und der Sprache dringend benötigte Flexibilität verleihen, lösen sie das Expression Problem von Haus aus nicht so, wie es das Featherweight Go-Paper ursprünglich vorgesehen hatte. Da Constraints auf Struct-Ebene statt auf Methodenebene definiert werden müssen, sind Entwickler weiterhin gezwungen, Interfaces im Voraus zu planen oder bestehenden Code anzupassen, wenn sie generische Typen um neue Operationen erweitern.
Lade die kuratierte Offline-Ausgabe von Kaues Lebensweise herunter – ein praktischer Leitfaden zur Optimierung von 🍏 Gesundheit, 💰 Wohlstand, ❤️ Liebe und 🧘 Sinn.
Erhalte eine Benachrichtigung, wenn ich neue Essays, Buchzusammenfassungen und praktische Leitfäden für ein gutes Leben veröffentliche. Kein Spam – jederzeit abbestellbar.
Hast du eine Frage, eine Perspektive oder einen Vorschlag? Teile deine Gedanken unten mit. Die Kommentare werden über GitHub Discussions bereitgestellt (werbefrei und spamfrei) – klicke unten auf Mit GitHub anmelden, um zu kommentieren oder zu reagieren (ein kostenloses GitHub-Konto ist in weniger als einer Minute erstellt).
Aktualisiert am 11.10.2026.
HAFTUNGSAUSSCHLUSS: Die Inhalte auf dieser Website dienen ausschließlich Informations- und Bildungszwecken und stellen keine medizinische, psychologische, finanzielle, steuerliche oder rechtliche Beratung sowie keine Anlageberatung dar. Die hier präsentierten Ideen, Zusammenstellungen und Meinungen sind meine eigenen und spiegeln nicht die meines Arbeitgebers wider. Zusammenfassungen von Büchern, Vorträgen und Artikeln sind unabhängige Bildungskommentare und bleiben geistiges Eigentum der jeweiligen Autoren. KI-Tools können zur Unterstützung bei der Bearbeitung, Formatierung und Synthese eingesetzt werden; obwohl größte Sorgfalt auf Richtigkeit gelegt wird, können Fehler auftreten. DATENSCHUTZ: Diese Website nutzt Dienste von Drittanbietern für optionale Newsletter-Abonnements (follow.it), Kommentare (giscus / GitHub Discussions) und eingebettete Medien (YouTube), die IP-Adressen und Cookies der Besucher gemäß ihren jeweiligen Datenschutzrichtlinien verarbeiten können.