Warum keine Generika in Go?

126

Haftungsausschluss: Ich habe jetzt nur einen Tag mit Go gespielt, daher besteht eine gute Chance, dass ich viel verpasst habe.

Weiß jemand, warum es in Go keine echte Unterstützung für Generika / Vorlagen / whatsInAName gibt? Es gibt also ein generisches mapProgramm, das jedoch vom Compiler bereitgestellt wird, während ein Go-Programmierer keine eigene Implementierung schreiben kann. Warum kann ich bei all dem Gerede darüber, Go so orthogonal wie möglich zu gestalten, einen generischen Typ verwenden, aber keinen neuen erstellen?

Besonders wenn es um funktionale Programmierung geht, gibt es Lambdas, sogar Schließungen, aber mit einem statischen Typsystem ohne Generika, wie schreibe ich generische Funktionen höherer Ordnung wie filter(predicate, list)? OK, verknüpfte Listen und dergleichen können unter interface{}Einbußen bei der Typensicherheit durchgeführt werden.

Da eine schnelle Suche in SO / Google keine Erkenntnisse ergab, sieht es so aus, als würden Generika, wenn überhaupt, nachträglich zu Go hinzugefügt. Ich vertraue darauf, dass Thompson es viel besser macht als die Java-Leute, aber warum sollten Generika draußen bleiben? Oder sind sie geplant und noch nicht umgesetzt?

s4y
quelle
Ich denke, es lohnt sich darauf hinzuweisen: Die Verwendung von interface {} beeinträchtigt nicht die Typensicherheit. Es handelt sich um einen Typ, der für andere Typen aktiviert (nicht umgewandelt) werden kann. Diese Zusicherungen rufen jedoch weiterhin Laufzeitprüfungen auf, um die Typensicherheit zu gewährleisten.
cthom06
12
interface{}opfert statische Sicherheit. Dies ist jedoch eine etwas seltsame Beschwerde, wenn das Erwähnen von Schema der nächste Absatz ist, da Schema normalerweise keine statische Typprüfung aufweist.
Poolie
@poolie: Es geht mir darum, mich an EIN Paradigma innerhalb einer Sprache zu halten. Entweder verwende ich statische Sicherheit XOR nicht.
2
Übrigens heißt es "Go", nicht "GO", wie Sie auf golang.org sehen können. Und es wird zwischen Groß- und Kleinschreibung unterschieden. :-)
Poolie
1
Wie wäre es mit github.com/facebookgo/generics ?
Thellimist

Antworten:

78

Diese Antwort finden Sie hier: http://golang.org/doc/faq#generics

Warum hat Go keine generischen Typen?

Generika können irgendwann hinzugefügt werden. Wir empfinden keine Dringlichkeit für sie, obwohl wir verstehen, dass einige Programmierer dies tun.

Generika sind praktisch, kosten jedoch Komplexität im Typensystem und zur Laufzeit. Wir haben noch kein Design gefunden, das einen der Komplexität angemessenen Wert bietet, obwohl wir weiterhin darüber nachdenken. Die integrierten Karten und Slices von Go sowie die Möglichkeit, die leere Schnittstelle zum Erstellen von Containern (mit explizitem Unboxing) zu verwenden, bedeuten in vielen Fällen, dass Code geschrieben werden kann, der das tut, was Generika ermöglichen würden, wenn auch weniger reibungslos.

Dies bleibt ein offenes Thema.

Vinzenz
quelle
14
@amoebe, "die leere Schnittstelle", geschrieben interface{}, ist der grundlegendste Schnittstellentyp, und jedes Objekt stellt ihn bereit. Wenn Sie einen Container erstellen, der sie enthält, kann er jedes (nicht primitive) Objekt akzeptieren. Es ist also einem Container Objectsin Java sehr ähnlich .
Poolie
4
@YinWang Generika sind in einer vom Typ abgeleiteten Umgebung nicht so einfach. Wichtiger; interface {} entspricht nicht void * -Pointern in C. Bessere Analogien wären die ID-Typen System.Object oder Objective-C von C #. Typinformationen bleiben erhalten und können (tatsächlich behauptet) auf ihren konkreten Typ zurück "gegossen" werden. Holen Sie sich die Details hier: golang.org/ref/spec#Type_assertions
tbone
2
Das System.Object von @tbone C # (oder Javas Objekt an sich) ist im Wesentlichen das, was ich mit "Cs ungültigen Zeigern" gemeint habe (wobei der Teil ignoriert wird, dass Sie in diesen Sprachen keine Zeigerarithmetik durchführen können). Hier gehen die statischen Typinformationen verloren. Eine Besetzung hilft nicht viel, da Sie einen Laufzeitfehler erhalten.
Ian
1
Die Vorlagen von @ChristopherPfohl D scheinen viel weniger Zeit für die Kompilierung zu haben, und normalerweise generieren Sie mit Vorlagen nicht mehr Code als sonst ( je nach den Umständen könnten Sie tatsächlich weniger Code erhalten).
Cubic
3
@ChristopherPfohl Ich denke, nur Java-Generika haben Boxing / Unboxing-Probleme für primitive Typen? C # 's reified generic hat das Problem nicht.
ca9163d9
32

Gehe 2

Unter https://blog.golang.org/go2draft finden Sie einen Entwurf für Generika .

Gehen Sie 1

Russ Cox, einer der Go-Veteranen, schrieb einen Blog-Beitrag mit dem Titel The Generic Dilemma , in dem er fragt

… Möchten Sie langsame Programmierer, langsame Compiler und aufgeblähte Binärdateien oder langsame Ausführungszeiten?

Langsame Programmierer sind das Ergebnis von keinen Generika. Langsame Compiler werden wie Generika durch C ++ verursacht. Langsame Ausführungszeiten ergeben sich aus dem von Java verwendeten Boxing-Unboxing-Ansatz.

Die vierte Möglichkeit, die im Blog nicht erwähnt wird, ist die C # -Route. Generieren des speziellen Codes wie in C ++, jedoch zur Laufzeit, wenn dies erforderlich ist. Ich mag es wirklich, aber Go ist ganz anders als C #, also ist dies wahrscheinlich überhaupt nicht anwendbar ...

Ich sollte erwähnen, dass die Verwendung der beliebten Java 1.4-ähnlichen Technik der generischen Programmierung in go , bei der Casts verwendet interface{}werden, neben dem Verlust der Sicherheit des Kompilierungszeittyps genau dieselben Probleme wie das Boxen-Entpacken aufweist (weil wir dies tun). Für kleine Typen (wie Ints) optimiert Go den interface{}Typ so, dass eine Liste von Ints, die in die Schnittstelle {} umgewandelt wurden, einen zusammenhängenden Speicherbereich belegt und nur doppelt so viel Speicherplatz benötigt wie normale Ints. Es gibt jedoch immer noch den Aufwand für Laufzeitprüfungen beim Casting von interface{}. Referenz .

Alle Projekte, die generische Unterstützung hinzufügen (es gibt mehrere davon und alle sind interessant), gehen einheitlich den C ++ - Weg der Generierung von Kompilierungszeitcode.

user7610
quelle
Meine Lösung für dieses Dilemma wäre, standardmäßig auf "langsame Ausführungszeiten" zu gehen und das Programm zu profilieren und die leistungsempfindlichsten Teile in einem Modus "langsame Compiler und aufgeblähte Binärdateien" neu zu kompilieren. Schade, dass Leute, die solche Dinge tatsächlich implementieren, dazu neigen, den C ++ - Weg zu gehen.
user7610
1
Es wurde erwähnt, dass kleine Typen (dh int), die im Speicher gespeichert sind []interface{}, 2x den RAM als verwenden []int. Während dies zutrifft, verwenden auch kleinere Typen (dh Bytes) bis zum 16-fachen des Arbeitsspeichers []byte.
BMiner
Es gibt eigentlich kein Dilemma mit dem C ++ - Ansatz. Wenn ein Programmierer Vorlagencode schreibt, muss der Vorteil die Kosten für die langsame Kompilierung übersteigen. Ansonsten könnte er es einfach so machen.
John Z. Li
Das Dilemma ist, welchen Ansatz man wählen soll. Wenn Sie das Dilemma mit dem C ++ - Ansatz lösen, ist das Dilemma gelöst.
user7610
9

Obwohl Generika derzeit nicht integriert sind, gibt es mehrere externe Implementierungen von Generika für unterwegs, die Kommentare in Kombination mit kleinen Dienstprogrammen verwenden, die Code generieren.

Hier ist eine solche Implementierung: http://clipperhouse.github.io/gen/

Alexander
quelle
1

Eigentlich laut diesem Beitrag:

Viele Leute sind (fälschlicherweise) zu dem Schluss gekommen, dass die Position des Go-Teams lautet: "Go wird niemals Generika haben." Im Gegenteil, wir verstehen die potenziellen Generika, die Go viel flexibler und leistungsfähiger machen und Go viel komplizierter machen. Wenn wir Generika hinzufügen möchten, möchten wir dies auf eine Weise tun, die so viel Flexibilität und Leistung bei möglichst geringer Komplexität bietet.

Ayush Gupta
quelle
-1

Parametrischer Polymorphismus (Generika) wird für Go 2 in Betracht gezogen .

Dieser Ansatz würde das Konzept eines Vertrags einführen, mit dem Einschränkungen für Typparameter ausgedrückt werden können:

contract Addable(a T) {
  a + a // Could be += also
}

Ein solcher Vertrag könnte dann folgendermaßen genutzt werden:

func Sum(type T Addable)(l []T) (total T) {
  for _, e := range l {
    total += e
  }
  return total
}

Dies ist ein Vorschlag in dieser Phase.


Ihre filter(predicate, list)Funktion könnte mit einem Typparameter wie dem folgenden implementiert werden:

func Filter(type T)(f func(T) bool, l []T) (result []T) {
  for _, e := range l {
    if f(e) {
      result = append(result, e)
    }
  }
  return result
}

In diesem Fall besteht keine Notwendigkeit zur Einschränkung T.

ᆼ ᆺ ᆼ
quelle
1
Wenn Sie diese Antwort heute lesen, beachten Sie, dass Verträge aus dem Entwurf des Vorschlags gestrichen wurden
jub0bs