Go: Vorschlag: Spezifikation: Unterstützung für typisierte Aufzählungen hinzufügen

Erstellt am 1. Apr. 2017  ·  180Kommentare  ·  Quelle: golang/go

Ich möchte vorschlagen, dass enum als besondere Art von type zu Go hinzugefügt wird. Die folgenden Beispiele sind dem protobuf-Beispiel entlehnt.

Enums heute in Go

type SearchRequest int
var (
    SearchRequestUNIVERSAL SearchRequest = 0 // UNIVERSAL
    SearchRequestWEB       SearchRequest = 1 // WEB
    SearchRequestIMAGES    SearchRequest = 2 // IMAGES
    SearchRequestLOCAL     SearchRequest = 3 // LOCAL
    SearchRequestNEWS      SearchRequest = 4 // NEWS
    SearchRequestPRODUCTS  SearchRequest = 5 // PRODUCTS
    SearchRequestVIDEO     SearchRequest = 6 // VIDEO
)

type SearchRequest string
var (
    SearchRequestUNIVERSAL SearchRequest = "UNIVERSAL"
    SearchRequestWEB       SearchRequest = "WEB"
    SearchRequestIMAGES    SearchRequest = "IMAGES"
    SearchRequestLOCAL     SearchRequest = "LOCAL"
    SearchRequestNEWS      SearchRequest = "NEWS"
    SearchRequestPRODUCTS  SearchRequest = "PRODUCTS"
    SearchRequestVIDEO     SearchRequest = "VIDEO"
)

// IsValid has to be called everywhere input happens, or you risk bad data - no guarantees
func (sr SearchRequest) IsValid() bool {
    switch sr {
        case SearchRequestUNIVERSAL, SearchRequestWEB...:
            return true
    }
    return false
}

Wie es mit Sprachunterstützung aussehen könnte

enum SearchRequest int {
    0 // UNIVERSAL
    1 // WEB
    2 // IMAGES
    3 // LOCAL
    4 // NEWS
    5 // PRODUCTS
    6 // VIDEO
}

enum SearchRequest string {
    "UNIVERSAL"
    "WEB"
    "IMAGES"
    "LOCAL"
    "NEWS"
    "PRODUCTS"
    "VIDEO"
}

Das Muster ist so häufig, dass ich denke, dass es eine spezielle Schreibweise rechtfertigt, und ich glaube, dass es den Code besser lesbar macht. Auf der Implementierungsebene würde ich mir vorstellen, dass die meisten Fälle zur Kompilierzeit überprüft werden können, von denen einige bereits heute auftreten, während andere nahezu unmöglich sind oder erhebliche Kompromisse erfordern.

  • Sicherheit für exportierte Typen : Nichts hindert jemanden daran, SearchRequest(99) oder SearchRequest("MOBILEAPP") zu tun. Aktuelle Problemumgehungen umfassen das Erstellen eines nicht exportierten Typs mit Optionen, aber das macht den resultierenden Code oft schwieriger zu verwenden / zu dokumentieren.
  • Laufzeitsicherheit : Genauso wie protobuf beim Unmarshaling auf Gültigkeit prüft, bietet dies eine sprachweite Validierung, wann immer eine Aufzählung instanziiert wird.
  • Werkzeuge / Dokumentation : Viele Pakete setzen heute gültige Optionen in Feldkommentare, aber nicht jeder tut es und es gibt keine Garantie dafür, dass die Kommentare nicht veraltet sind.

Dinge, die man beachten muss

  • Nil : Durch die Implementierung von enum über dem Typsystem glaube ich nicht, dass dies eine spezielle Groß-/Kleinschreibung erfordern sollte. Wenn jemand möchte, dass nil gültig ist, dann sollte die Aufzählung als Zeiger definiert werden.
  • Standardwert-/Laufzeitzuweisungen : Dies ist eine der schwierigeren Entscheidungen. Was ist, wenn der Go-Standardwert nicht als gültige Aufzählung definiert ist? Die statische Analyse kann einiges davon zur Kompilierzeit abmildern, aber es müsste eine Möglichkeit geben, mit externen Eingaben umzugehen.

Ich habe keine starken Meinungen über die Syntax. Ich glaube, dass dies gut gemacht werden könnte und sich positiv auf das Ökosystem auswirken würde.

Go2 LanguageChange NeedsInvestigation Proposal

Hilfreichster Kommentar

@md2perpe das sind keine Aufzählungen.

  1. Sie können nicht aufgezählt, iteriert werden.
  2. Sie haben keine nützliche Zeichenfolgendarstellung.
  3. Sie haben keine Identität:

„Geh
Paket Haupt

importieren (
"fmt"
)

func main() {
Geben Sie SearchRequest int ein
konstant (
Universelle Suchanfrage = iota
Netz
)

const (
    Another SearchRequest = iota
    Foo
)

fmt.Println("Should be false: ", (Web == Foo))
    // Prints: "Should be false:  true"

}
````

Ich stimme @derekperkins voll und ganz zu, dass Go als erstklassiger Bürger eine Aufzählung braucht. Wie das aussehen würde, bin ich mir nicht sicher, aber ich vermute, dass es möglich wäre , ohne das Glashaus von Go 1 zu zerstören.

Alle 180 Kommentare

@derekparker es gibt eine Diskussion darüber, einen Go2-Vorschlag in #19412 zu machen

Ich habe mir das heute früher durchgelesen, aber das schien sich mehr auf gültige Typen zu konzentrieren, wo sich dies auf gültige Typwerte konzentriert. Vielleicht ist dies eine Teilmenge dieses Vorschlags, aber auch eine weniger weitreichende Änderung des Typensystems, die heute in Go eingeführt werden könnte.

Aufzählungen sind ein Sonderfall von Summentypen, bei denen alle Typen gleich sind und jedem durch eine Methode ein Wert zugeordnet ist. Sicherlich mehr zu tippen, aber gleicher Effekt. Unabhängig davon wäre es das eine oder andere, Summentypen decken mehr Boden ab, und selbst Summentypen sind unwahrscheinlich. Wegen der Go1-Kompatibilitätsvereinbarung passiert ohnehin nichts bis Go2, da diese Vorschläge zumindest ein neues Schlüsselwort erfordern würden, sollte einer von ihnen angenommen werden

Fair genug, aber keiner dieser Vorschläge bricht das Kompatibilitätsabkommen. Es wurde die Meinung geäußert, dass Summentypen "zu groß" seien, um sie Go1 hinzuzufügen. Wenn das der Fall ist, dann ist dieser Vorschlag ein wertvoller Mittelweg, der ein Sprungbrett zu Vollsummentypen in Go2 sein könnte.

Beide erfordern ein neues Schlüsselwort, das den gültigen Go1-Code brechen würde, wenn es dieses als Kennung verwendet

Ich denke, das könnte man umgehen

Eine neue Sprachfunktion braucht überzeugende Anwendungsfälle. Alle Sprachfeatures sind nützlich, sonst würde sie niemand vorschlagen; Die Frage ist: Sind sie nützlich genug, um zu rechtfertigen, die Sprache zu verkomplizieren und von jedem zu verlangen, die neuen Konzepte zu lernen? Was sind hier die überzeugenden Anwendungsfälle? Wie werden die Menschen diese nutzen? Würden die Leute zum Beispiel erwarten, dass sie in der Lage sein werden, über den Satz gültiger Aufzählungswerte zu iterieren, und wenn ja, wie würden sie das tun? Bietet dieser Vorschlag mehr als nur das Vermeiden des Hinzufügens von Standardfällen zu einigen Schaltern?

Hier ist die idiomatische Art, Aufzählungen im aktuellen Go zu schreiben:

type SearchRequest int

const (
    Universal SearchRequest = iota
    Web
    Images
    Local
    News
    Products
    Video
)

Dies hat den Vorteil, dass es einfach ist, Flags zu erstellen, die ODER-verknüpft werden können (mit dem Operator | ):

type SearchRequest int

const (
    Universal SearchRequest = 1 << iota
    Web
    Images
    Local
    News
    Products
    Video
)

Ich kann nicht erkennen, dass die Einführung eines Schlüsselworts enum es viel kürzer machen würde.

@md2perpe das sind keine Aufzählungen.

  1. Sie können nicht aufgezählt, iteriert werden.
  2. Sie haben keine nützliche Zeichenfolgendarstellung.
  3. Sie haben keine Identität:

„Geh
Paket Haupt

importieren (
"fmt"
)

func main() {
Geben Sie SearchRequest int ein
konstant (
Universelle Suchanfrage = iota
Netz
)

const (
    Another SearchRequest = iota
    Foo
)

fmt.Println("Should be false: ", (Web == Foo))
    // Prints: "Should be false:  true"

}
````

Ich stimme @derekperkins voll und ganz zu, dass Go als erstklassiger Bürger eine Aufzählung braucht. Wie das aussehen würde, bin ich mir nicht sicher, aber ich vermute, dass es möglich wäre , ohne das Glashaus von Go 1 zu zerstören.

@md2perpe iota ist eine sehr begrenzte Möglichkeit, sich Enumerationen zu nähern, was für eine begrenzte Anzahl von Umständen hervorragend funktioniert.

  1. Sie benötigen einen int
  2. Sie müssen nur innerhalb Ihres Pakets konsistent sein und nicht den externen Zustand darstellen

Sobald Sie einen String oder einen anderen Typ darstellen müssen, was bei externen Flags sehr üblich ist, funktioniert iota nicht für Sie. Wenn Sie mit einer externen/Datenbankdarstellung übereinstimmen möchten, würde ich iota nicht verwenden, da dann die Bestellung im Quellcode wichtig ist und eine Neuordnung zu Problemen mit der Datenintegrität führen würde.

Dies ist nicht nur ein Bequemlichkeitsproblem, um den Code kürzer zu machen. Dies ist ein Vorschlag, der eine Datenintegrität auf eine Weise ermöglicht, die von der heutigen Sprache nicht durchsetzbar ist.

@ianlancetaylor

Würden die Leute zum Beispiel erwarten, dass sie in der Lage sein werden, über den Satz gültiger Aufzählungswerte zu iterieren, und wenn ja, wie würden sie das tun?

Ich denke, das ist ein solider Anwendungsfall, wie von @bep erwähnt. Ich denke, die Iteration würde wie eine Standard-Go-Schleife aussehen, und ich denke, sie würden in der Reihenfolge durchlaufen, in der sie definiert wurden.

for i, val := range SearchRequest {
...
}

Wenn Go mehr als Jota hinzufügen würde, warum dann nicht algebraische Datentypen verwenden?

Durch die Erweiterung der Bestellung gemäß der Definitionsreihenfolge und nach dem Beispiel von protobuf denke ich, dass der Standardwert des Felds das erste definierte Feld wäre.

@bep Nicht so bequem, aber Sie können alle diese Eigenschaften erhalten:

package main

var SearchRequests []SearchRequest
type SearchRequest struct{ name string }
func (req SearchRequest) String() string { return req.name }

func Request(name string) SearchRequest {
    req := SearchRequest{name}
    SearchRequests = append(SearchRequests, req)
    return req
}

var (
    Universal = Request("Universal")
    Web       = Request("Web")

    Another = Request("Another")
    Foo     = Request("Foo")
)

func main() {
    fmt.Println("Should be false: ", (Web == Foo))
    fmt.Println("Should be true: ", (Web == Web))
    for i, req := range SearchRequests {
        fmt.Println(i, req)
    }
}

Ich denke nicht, dass zur Kompilierzeit überprüfte Aufzählungen eine gute Idee sind. Ich glaube, go hat das im Moment ziemlich genau . Meine Begründung ist

  • Kompilierzeit geprüfte Aufzählungen sind weder abwärts- noch aufwärtskompatibel für den Fall von Hinzufügungen oder Entfernungen. #18130 wendet erhebliche Anstrengungen auf, um eine schrittweise Codereparatur zu ermöglichen; Aufzählungen würden diese Bemühungen zunichte machen; Jedes Paket, das jemals einen Satz von Aufzählungen ändern möchte, würde automatisch und zwangsweise alle seine Importer zerstören.
  • Im Gegensatz zu dem, was der ursprüngliche Kommentar behauptet, überprüft protobuf (aus diesem speziellen Grund) nicht wirklich die Gültigkeit von Enum-Feldern. proto2 gibt an, dass ein unbekannter Wert für eine Aufzählung wie ein unbekanntes Feld behandelt werden soll und proto3 gibt sogar an, dass der generierte Code eine Möglichkeit haben muss, sie mit dem codierten Wert darzustellen (genau wie go es derzeit mit gefälschten Aufzählungen tut).
  • Am Ende bringt es nicht wirklich viel. Sie können eine Stringifizierung mit dem Stringer-Tool erhalten. Sie können eine Iteration erhalten, indem Sie eine Sentinel-MaxValidFoo-Konstante hinzufügen (aber siehe den obigen Vorbehalt. Sie sollten nicht einmal die Anforderung haben). Sie sollten die beiden const-decls überhaupt nicht haben . Integrieren Sie einfach ein Tool in Ihr CI, das dies prüft.
  • Ich glaube nicht, dass andere Typen als ints tatsächlich notwendig sind. Das Stringer-Tool sollte bereits das Konvertieren von und zu Strings abdecken; Am Ende würde der generierte Code dem entsprechen, was ein Compiler ohnehin generieren würde (es sei denn, Sie schlagen ernsthaft vor, dass jeder Vergleich mit "String-Enums" die Bytes iterieren würde ...)

Insgesamt nur eine riesige -1 für mich. Es fügt nicht nur nichts hinzu; es tut aktiv weh.

Ich denke, die aktuelle Enum-Implementierung in Go ist sehr einfach und bietet genügend Überprüfungen der Kompilierungszeit. Ich erwarte eigentlich eine Art Rust-Enum mit grundlegendem Musterabgleich, aber es bricht möglicherweise Go1-Garantien.

Da Enums ein Sonderfall von Summentypen sind und die allgemeine Meinung ist, dass wir Schnittstellen verwenden sollten, um Summentypen zu simulieren, lautet die Antwort eindeutig https://play.golang.org/p/1BvOakvbj2

(falls es nicht klar ist: Ja, das ist ein Witz – in klassischer Programmierer-Manier bin ich um eins dran).

Im Ernst, für die in diesem Thread diskutierten Funktionen wären einige zusätzliche Werkzeuge nützlich.

Wie das Stringer-Tool könnte ein "Ranger"-Tool das Äquivalent der Iter -Funktion im oben verlinkten Code generieren.

Irgendetwas könnte {Binary,Text}{Marshaler,Unmarshaler}-Implementierungen generieren, damit sie einfacher über die Leitung gesendet werden können.

Ich bin mir sicher, dass es viele kleine Dinge wie diese gibt, die gelegentlich sehr nützlich wären.

Es gibt einige Vetting/Linter-Tools zur Vollständigkeitsprüfung von mit Schnittstellen simulierten Summentypen. Kein Grund, warum es keine für Iota-Enumerationen geben könnte, die Ihnen mitteilen, wenn Fälle übersehen oder ungültige, nicht typisierte Konstanten verwendet werden (vielleicht sollte es nur etwas anderes als 0 melden?).

Hier gibt es sicherlich auch ohne Sprachänderungen Raum für Verbesserungen.

Enums würden das bereits etablierte Typsystem ergänzen. Wie die vielen Beispiele in dieser Ausgabe gezeigt haben, sind die Bausteine ​​für Enumerationen bereits vorhanden. So wie Kanäle Abstraktionen auf hoher Ebene sind, die auf primitiveren Typen aufbauen, sollten Aufzählungen auf die gleiche Weise erstellt werden. Menschen sind arrogant, ungeschickt und vergesslich, Mechanismen wie Enums helfen menschlichen Programmierern, weniger Programmierfehler zu machen.

@bep Ich muss allen drei Punkten widersprechen. Idiomatische Go-Enumerationen ähneln stark C-Enumerationen, die keine Iteration gültiger Werte haben, keine automatische Konvertierung in Zeichenfolgen haben und nicht unbedingt eine eindeutige Identität haben.

Iteration ist schön zu haben, aber in den meisten Fällen ist es in Ordnung, Konstanten für den ersten und den letzten Wert zu definieren, wenn Sie eine Iteration wünschen. Sie können dies sogar auf eine Weise tun, die keine Aktualisierung erfordert, wenn Sie neue Werte hinzufügen, da iota es automatisch eins nach dem Ende macht. Die Situation, in der die Sprachunterstützung einen bedeutenden Unterschied machen würde, ist, wenn die Werte der Aufzählung nicht zusammenhängend sind.

Die automatische Konvertierung in Strings ist nur ein kleiner Wert: Insbesondere in diesem Vorschlag müssen die Stringwerte so geschrieben werden, dass sie den int-Werten entsprechen, sodass es wenig zu gewinnen gibt, wenn Sie explizit selbst ein Array von Stringwerten schreiben. In einem alternativen Vorschlag könnte es mehr wert sein, aber es gibt auch Nachteile, Variablennamen zu zwingen, String-Darstellungen zu entsprechen.

Schließlich bin ich mir nicht einmal sicher, ob eine eindeutige Identität überhaupt eine nützliche Funktion ist. Enums sind keine Summentypen wie beispielsweise in Haskell. Sie sind benannte Nummern. Die Verwendung von Aufzählungen als Flag-Werte ist beispielsweise üblich. Zum Beispiel können Sie ReadWriteMode = ReadMode | WriteMode haben und das ist eine nützliche Sache. Es ist durchaus möglich, auch andere Werte zu haben, zum Beispiel könnten Sie DefaultMode = ReadMode haben. Es ist nicht so, dass irgendeine Methode jemanden davon abhalten könnte, const DefaultMode = ReadMode zu schreiben; Welchen Zweck hat es, dies in einer gesonderten Erklärung zu verlangen?

@bep Ich muss allen drei Punkten widersprechen. Idiomatische Go-Enumerationen ähneln stark C-Enumerationen, die keine Iteration gültiger Werte haben, keine automatische Konvertierung in Zeichenfolgen haben und nicht unbedingt eine eindeutige Identität haben.

@alercah , bitte ziehen Sie dieses idomatic Go nicht als angebliches "gewinnendes Argument" in eine Diskussion; Go hat keine eingebauten Enums, daher macht es wenig Sinn, über einige nicht existierende Idoms zu sprechen.

Go wurde entwickelt, um ein besseres C/C++ oder ein weniger ausführliches Java zu sein, daher wäre ein Vergleich mit letzterem sinnvoller. Und Java hat ein eingebautes Enum type ("Java-Programmiersprachen-Aufzählungstypen sind viel mächtiger als ihre Gegenstücke in anderen Sprachen. "): https://docs.oracle.com/javase/tutorial/java /javaOO/enum.html

Und auch wenn Sie mit dem »viel mächtigeren Teil« nicht einverstanden sein mögen, hat der Java-Typ Enum alle drei von mir erwähnten Eigenschaften.

Ich kann das Argument verstehen, dass Go schlanker, einfacher usw. ist und dass einige Kompromisse eingegangen werden müssen, damit es so bleibt, und ich habe in diesem Thread einige hackige Workarounds gesehen, die irgendwie funktionieren, aber ein Satz von iota ints allein machen keine Aufzählung.

Aufzählungen und automatische Zeichenfolgenkonvertierungen sind gute Kandidaten für die Funktion „Go Generate“. Wir haben bereits einige Lösungen. Java-Enumerationen sind etwas in der Mitte klassischer Enumerationen und Summentypen. Also meiner Meinung nach ein schlechtes Sprachdesign.
Die Sache mit idiomatischem Go ist der Schlüssel, und ich sehe keine starken Gründe, alle Funktionen von Sprache X in Sprache Y zu kopieren, nur weil jemand damit vertraut ist.

Enum-Typen der Java-Programmiersprache sind viel leistungsfähiger als ihre Gegenstücke in anderen Sprachen

Das galt vor einem Jahrzehnt. Sehen Sie sich die moderne Nullkosten-Implementierung von Option in Rust an, die von Summentypen und Musterabgleich unterstützt wird.

Die Sache mit idiomatischem Go ist der Schlüssel, und ich sehe keine starken Gründe, alle Funktionen von Sprache X in Sprache Y zu kopieren, nur weil jemand damit vertraut ist.

Beachten Sie, dass ich den hier gegebenen Schlussfolgerungen nicht allzu sehr widerspreche, aber die Verwendung von _ idiomatischem Go_ stellt Go auf ein künstlerisches Podest. Die meiste Softwareprogrammierung ist ziemlich langweilig und praktisch. Und oft müssen Sie nur eine Dropdown-Box mit einer Aufzählung füllen ...

//go:generate enumerator Foo,Bar
Einmal geschrieben, überall verfügbar. Beachten Sie, dass das Beispiel abstrakt ist.

@bep Ich glaube, du hast den ursprünglichen Kommentar falsch gelesen. "Go idiomatic enums" sollte sich auf die aktuelle Konstruktion der Verwendung des Typs Foo int + const-decl + iota beziehen, glaube ich, um nicht zu sagen, "was auch immer Sie vorschlagen, ist nicht idiomatisch".

@rsc In Bezug auf das Label Go2 widerspricht das meiner Begründung für die Einreichung dieses Vorschlags. #19412 ist ein Vorschlag für vollständige Summentypen, der eine mächtigere Obermenge ist als mein einfacher Enum-Vorschlag hier, und ich würde das lieber in Go2 sehen. Aus meiner Sicht ist die Wahrscheinlichkeit, dass Go2 in den nächsten 5 Jahren stattfindet, gering, und ich würde lieber sehen, dass etwas in einem kürzeren Zeitrahmen passiert.

Wenn mein Vorschlag eines neuen reservierten Schlüsselworts enum für BC unmöglich ist, gibt es noch andere Möglichkeiten, es zu implementieren, sei es eine vollständige Sprachintegration oder in go vet integrierte Tools. Wie ich ursprünglich sagte, bin ich nicht besonders in Bezug auf die Syntax, aber ich bin fest davon überzeugt, dass es eine wertvolle Ergänzung zu Go heute wäre, ohne eine erhebliche kognitive Belastung für neue Benutzer hinzuzufügen.

Ein neues Keyword ist vor Go 2 nicht möglich. Es wäre ein klarer Verstoß gegen die Kompatibilitätsgarantie von Go 1.

Persönlich sehe ich noch nicht die überzeugenden Argumente für Enum oder, was das angeht, für Summentypen, nicht einmal für Go 2. Ich sage nicht, dass das nicht passieren kann. Aber eines der Ziele der Go-Sprache ist die Einfachheit der Sprache. Es reicht nicht aus, dass eine Sprachfunktion nützlich ist; alle Sprachfunktionen sind nützlich – wenn sie nicht nützlich wären, würde niemand sie vorschlagen. Um eine Funktion zu Go hinzuzufügen, muss die Funktion genügend überzeugende Anwendungsfälle haben, damit es sich lohnt, die Sprache zu verkomplizieren. Die überzeugendsten Anwendungsfälle sind Code, der ohne das Feature nicht geschrieben werden kann, zumindest jetzt ohne große Umständlichkeit.

Ich würde gerne Enums in Go sehen. Ich möchte ständig meine exponierte API einschränken (oder mit einer eingeschränkten API außerhalb meiner App arbeiten), in der es eine begrenzte Anzahl gültiger Eingaben gibt. Für mich ist dies der perfekte Ort für eine Aufzählung.

Beispielsweise könnte ich eine Client-App erstellen, die eine Verbindung zu einer Art API im RPC-Stil herstellt und über einen bestimmten Satz von Aktionen / Opcodes verfügt. Ich kann dafür const s verwenden, aber nichts hindert irgendjemanden (mich eingeschlossen!) daran, einfach einen ungültigen Code zu senden.

Auf der anderen Seite, wenn ich die Serverseite für dieselbe API schreibe, wäre es schön, eine switch-Anweisung für die Aufzählung schreiben zu können, die einen Compilerfehler (oder zumindest einige go vet Warnungen), wenn nicht alle möglichen Werte der Aufzählung überprüft werden (oder zumindest ein default: existiert).

Ich denke, dies (Enumerationen) ist ein Bereich, in dem Swift wirklich richtig liegt.

Ich könnte eine Client-App erstellen, die eine Verbindung zu einer Art API im RPC-Stil herstellt und über einen bestimmten Satz von Aktionen / Opcodes verfügt. Ich kann dafür consts verwenden, aber nichts hindert irgendjemanden (mich eingeschlossen!) daran, einfach einen ungültigen Code zu senden.

Dies ist eine schreckliche Idee, um mit Aufzählungen zu lösen. Dies würde bedeuten, dass Sie jetzt niemals einen neuen Enum-Wert hinzufügen können, da plötzlich RPCs ausfallen könnten oder Ihre Daten beim Rollback unlesbar werden. Der Grund, warum proto3 verlangt, dass generierter Enum-Code einen „unbekannten Code“-Wert unterstützt, ist, dass dies eine Lektion ist, die durch Schmerzen gelernt wurde (vergleichen Sie es damit, wie proto2 dies gelöst hat, was besser, aber immer noch sehr schlecht ist). Sie möchten, dass die Anwendung diesen Fall ordnungsgemäß handhaben kann.

@Merovius Ich respektiere deine Meinung, bin aber höflich anderer Meinung. Sicherzustellen, dass nur gültige Werte verwendet werden, ist einer der Hauptzwecke von Aufzählungen.

Enums sind nicht für jede Situation geeignet, aber für einige sind sie großartig! Die richtige Versionsverwaltung und Fehlerbehandlung sollte in den meisten Situationen mit neuen Werten umgehen können.

Für den Umgang mit externen Prozessen ist ein Oh-Oh-Zustand sicherlich ein Muss.

Mit Aufzählungen (oder den allgemeineren und nützlicheren Summentypen) können Sie der Summe/Aufzählung einen expliziten "unbekannten" Code hinzufügen, mit dem Sie der Compiler umgehen muss (oder diese Situation einfach vollständig am Endpunkt behandeln, wenn Sie nur etwas tun können). protokollieren und mit der nächsten Anfrage fortfahren).

Ich finde Summentypen nützlicher für innerhalb eines Prozesses, wenn ich weiß, dass ich X Fälle habe, mit denen ich mich befassen muss. Für kleines X ist es nicht schwer zu handhaben, aber für großes X schätze ich es, wenn der Compiler mich anbrüllt, besonders beim Refactoring.

Über API-Grenzen hinweg gibt es weniger Anwendungsfälle, und man sollte sich immer auf die Seite der Erweiterbarkeit irren, aber manchmal hat man etwas, das wirklich immer nur eines von X Dingen sein kann, wie bei einem AST oder trivialeren Beispielen wie einem "Tag der der Wochenwert, wo der Bereich an dieser Stelle ziemlich festgelegt ist (bis auf die Wahl des Kalendersystems).

@jimmyfrasche Ich gebe dir vielleicht den Wochentag, aber nicht AST. Grammatiken entwickeln sich. Was heute ungültig sein könnte, könnte morgen vollständig gültig sein, und das könnte das Hinzufügen neuer Knotentypen zum AST beinhalten. Bei Compiler-geprüften Summentypen wäre dies ohne Brüche nicht möglich.

Und ich verstehe nicht, warum das nicht einfach durch einen Tierarztcheck gelöst werden kann; Ihnen eine perfekt geeignete statische Überprüfung umfangreicher Gehäuse zu geben und mir die Möglichkeit einer schrittweisen Reparatur zu geben.

Ich spiele herum mit der Implementierung eines Clients für eine Server-API. Einige der Argumente und Rückgabewerte sind Aufzählungen in der API. Es gibt insgesamt 45 Aufzählungstypen.

Die Verwendung von Aufzählungskonstanten in Go ist in meinem Fall nicht möglich, da einige der Werte für verschiedene Aufzählungstypen denselben Namen haben. Im Beispiel unten erscheint Destroy zweimal, sodass der Compiler den Fehler Destroy redeclared in this block .

type TaskAllowedOperations int
const (
    _ TaskAllowedOperations = iota
    Cancel
    Destroy
)

type OnNormalExit int
const (
    _ OnNormalExit = iota
    Destroy
    Restart
)

Daher muss ich mir eine andere Darstellung überlegen. Idealerweise eine, die es einer IDE ermöglicht, die möglichen Werte für einen bestimmten Typ anzuzeigen, damit die Benutzer des Clients es einfacher haben, ihn zu verwenden. Enum als Bürger erster Klasse in Go zu haben, würde dem genügen.

@kongslund Ich weiß, es ist keine perfekte Implementierung, aber ich habe gerade einen Codegenerator erstellt, der für Sie von Interesse sein könnte. Es erfordert nur, dass Sie Ihre Aufzählung in einem Kommentar über der Typdeklaration deklarieren und den Rest für Sie generieren.

// ENUM(_, Cancel, Destroy)
type TaskAllowedOperations int

// ENUM(_, Destroy, Restart)
type OnNormalExit int

Generieren würde

const(
  _ TaskAllowedOperations = iota
  TaskAllowedOperationsCancel
  TaskAllowedOperationsDestroy
)

const(
  _ OnNormalExit = iota
  OnNormalExitDestroy
  OnNormalExitRestart
)

Der bessere Teil ist, dass es String() Methoden generieren würde, die das Präfix in ihnen ausschließen, sodass Sie "Destroy" entweder als TaskAllowedOperations oder OnNormalExit parsen können.

https://github.com/abice/go-enum

Jetzt wo der Stecker weg ist...

Mir persönlich macht es nichts aus, dass Aufzählungen nicht Teil der Go-Sprache sind, was nicht meine ursprüngliche Einstellung zu dieser Angelegenheit war. Als ich zum ersten Mal kam, um zu gehen, hatte ich oft eine verwirrte Reaktion, warum so viele Entscheidungen getroffen wurden. Aber nachdem Sie die Sprache verwendet haben, ist es schön, die Einfachheit zu haben, an der sie festhält, und wenn etwas Zusätzliches benötigt wird, stehen die Chancen gut, dass jemand anderes es auch benötigt und ein großartiges Paket erstellt hat, um bei diesem speziellen Problem zu helfen. Behalte die Menge an Cruft in meinem Ermessen.

In dieser Diskussion wurden viele gültige Punkte angesprochen, einige für die Unterstützung von Enums und auch viele dagegen (zumindest soweit der Vorschlag etwas darüber aussagt, was "Enums" überhaupt sind). Ein paar Dinge, die mir aufgefallen sind:

  • Das einleitende Beispiel (Enums in Go heute) ist irreführend: Dieser Code wird generiert und fast niemand würde so einen Go-Code von Hand schreiben. Tatsächlich ist der Vorschlag (Wie es mit Sprachunterstützung aussehen könnte) viel näher an dem, was wir tatsächlich bereits in Go tun.

  • @jediorange erwähnt, dass Swift "wirklich (Enumerationen) richtig gemacht hat": Wie dem auch sei, aber Swift-Enumerationen sind ein überraschend kompliziertes Biest, das alle möglichen Konzepte miteinander vermischt. In Go haben wir bewusst auf Mechanismen verzichtet, die sich mit anderen Sprachmerkmalen überschneiden und dafür mehr Orthogonalität erhalten. Die Konsequenz für einen Programmierer ist, dass er sich nicht entscheiden muss, welches Feature er verwenden soll: eine Aufzählung oder eine Klasse oder einen Summentyp (falls vorhanden) oder eine Schnittstelle.

  • Der Punkt von @ianlancetaylor über die Nützlichkeit von Sprachfunktionen darf nicht auf die leichte Schulter genommen werden. Es gibt unzählige nützliche Funktionen; Die Frage ist, welche wirklich überzeugend und ihren Preis wert sind (zusätzliche Komplexität der Sprache und damit Lesbarkeit und Implementierung).

  • Als Nebensache sind iota-definierte Konstanten in Go natürlich nicht auf ints beschränkt. Solange sie Konstanten sind, beschränken sie sich auf (möglicherweise benannte) Grundtypen (inkl. Floats, Booleans, Strings: https://play.golang.org/p/lhd3jqqg5z).

  • @merovius weist auf die Einschränkungen von (statischen!) Kompilierzeitprüfungen hin. Ich bezweifle sehr, dass Aufzählungen, die nicht erweitert werden können, in Situationen geeignet sind, in denen eine Erweiterung wünschenswert oder erwartet wird (jede langlebige API-Oberfläche entwickelt sich im Laufe der Zeit).

Das bringt mich zu einigen Fragen zu diesem Vorschlag, die meines Erachtens beantwortet werden müssen, bevor es zu nennenswerten Fortschritten kommen kann:

1) Was sind die tatsächlichen Erwartungen für Enums wie vorgeschlagen? @bep erwähnt Aufzählbarkeit, Iterierbarkeit, Zeichenfolgendarstellungen, Identität. Ist da mehr? Gibt es weniger?

2) Unter der Annahme der Liste in 1), können Aufzählungen erweitert werden? Wenn das so ist, wie? (in demselben Paket? einem anderen Paket?) Wenn sie nicht erweitert werden können, warum nicht? Warum ist das in der Praxis kein Problem?

3) Namensraum: In Swift führt ein Enum-Typ einen neuen Namensraum ein. Es gibt eine bedeutende Maschinerie (syntaktischer Zucker, Typableitung), sodass der Namespace-Name nicht überall wiederholt werden muss. Beispielsweise kann man für Aufzählungswerte eines Aufzählungsmonats im richtigen Kontext .Januar anstelle von Monat.Januar schreiben (oder noch schlimmer, MyPackage.Month.January). Wird ein Enum-Namespace benötigt? Wenn ja, wie wird ein Enum-Namespace erweitert? Welche Art von syntaktischem Zucker ist erforderlich, damit dies in der Praxis funktioniert?

4) Sind Aufzählungswerte Konstanten? Unveränderliche Werte?

5) Welche Art von Operationen sind mit Aufzählungswerten möglich (z. B. außer Iteration): Kann ich einen vorwärts und einen rückwärts verschieben? Sind zusätzliche integrierte Funktionen oder Operatoren erforderlich? (Möglicherweise sind nicht alle Iterationen in Ordnung). Was passiert, wenn man über den letzten Aufzählungswert hinausgeht? Ist das ein Laufzeitfehler?

(Ich habe meine Formulierung des nächsten Absatzes in https://github.com/golang/go/issues/19814#issuecomment-322771922 korrigiert. Entschuldigung für die nachlässige Wortwahl unten.)

Ohne zu versuchen, diese Fragen tatsächlich zu beantworten, ist dieser Vorschlag bedeutungslos ("Ich möchte Aufzählungen, die tun, was ich will" ist kein Vorschlag).

Ohne den Versuch, diese Fragen tatsächlich zu beantworten, ist dieser Vorschlag bedeutungslos

@griesemer Sie haben eine Reihe großartiger Punkte / Fragen - aber diesen Vorschlag als bedeutungslos zu bezeichnen, weil er diese Fragen nicht beantwortet, macht wenig Sinn. Die Messlatte für Beiträge ist in diesem Projekt hoch, aber es sollte erlaubt sein, _etwas vorzuschlagen_, ohne einen Doktortitel in Compilern zu haben, und ein Vorschlag sollte kein _implementierungsbereites_ Design sein.

  • Go brauchte diesen Vorschlag, da er eine dringend benötigte Diskussion auslöste => Wert und Bedeutung
  • Wenn es auch zu Vorschlag Nr. 21473 führt => Wert und Bedeutung

Das einleitende Beispiel (Enums in Go heute) ist irreführend: Dieser Code wird generiert und fast niemand würde so einen Go-Code von Hand schreiben. Tatsächlich ist der Vorschlag (Wie es mit Sprachunterstützung aussehen könnte) viel näher an dem, was wir tatsächlich bereits in Go tun.

@griesemer Da muss ich widersprechen. Ich hätte nicht die vollständige Großschreibung im Go-Variablennamen belassen sollen, aber es gibt viele Stellen, an denen handgeschriebener Code fast identisch mit meinem Vorschlag aussieht, der von Googlern geschrieben wurde, die ich in der Go-Community respektiere. Wir folgen in unserer Codebasis ziemlich oft dem gleichen Muster. Hier ist ein Beispiel aus der Google Cloud Go-Bibliothek.

// ACLRole is the level of access to grant.
type ACLRole string

const (
    RoleOwner  ACLRole = "OWNER"
    RoleReader ACLRole = "READER"
    RoleWriter ACLRole = "WRITER"
)

Sie verwenden das gleiche Konstrukt an mehreren Stellen.
https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/bigquery/table.go#L78-L116
https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/storage/acl.go#L27-L49

Später gab es einige Diskussionen darüber, wie Sie die Dinge knapper machen können, wenn Sie iota verwenden, was für sich genommen nützlich sein kann, aber für einen begrenzten Anwendungsfall. Siehe meinen vorherigen Kommentar für weitere Details. https://github.com/golang/go/issues/19814#issuecomment -290948187

@bep Fairer Punkt; Ich entschuldige mich für meine unvorsichtige Wortwahl. Lassen Sie es mich noch einmal versuchen und hoffentlich meinen letzten Absatz oben respektvoller und klarer formulieren:

Um sinnvolle Fortschritte erzielen zu können, sollten die Befürworter dieses Vorschlags meines Erachtens versuchen, etwas genauer zu beschreiben, was ihrer Meinung nach wichtige Merkmale von Aufzählungen sind (z. B. indem sie einige der Fragen in https://github. com/golang/go/issues/19814#issuecomment-322752526). Aus der bisherigen Diskussion werden die gewünschten Features nur recht vage beschrieben.

Vielleicht wäre es als erster Schritt wirklich nützlich, Fallstudien zu haben, die zeigen, wie das vorhandene Go (erheblich) zu kurz kommt und wie Aufzählungen ein Problem besser/schneller/klarer usw. lösen würden. Siehe auch den ausgezeichneten Vortrag von @rsc auf der Gophercon bezüglich Go2 Sprachänderungen.

@derekperkins Ich würde diese (typisierten) Konstantendefinitionen nennen, keine Aufzählungen. Ich vermute, unsere Meinungsverschiedenheit beruht auf einem unterschiedlichen Verständnis dessen, was ein "Enum" sein soll, daher meine obigen Fragen.

(Mein vorheriger https://github.com/golang/go/issues/19814#issuecomment-322774830 hätte natürlich an @derekperkins gehen sollen, nicht an @derekparker. Autocomplete hat mich besiegt.)

Nach dem Kommentar von @derekperkins zu urteilen und meine eigenen Fragen teilweise zu beantworten, schließe ich, dass ein Go "enum" mindestens die folgenden Eigenschaften haben sollte:

  • Möglichkeit, eine Reihe von Werten unter einem (neuen) Typ zu gruppieren
  • Machen Sie es einfach, Namen (und entsprechende Werte, falls vorhanden) mit diesem Typ zu deklarieren, mit minimalem syntaktischen Overhead oder Boilerplate
  • Möglichkeit, diese Werte in aufsteigender Deklarationsreihenfolge zu durchlaufen

Klingt das richtig? Wenn ja, was muss dieser Liste noch hinzugefügt werden?

Deine Fragen sind alle gut.

Was sind die tatsächlichen Erwartungen für Enums wie vorgeschlagen? @bep erwähnt Aufzählbarkeit, Iterierbarkeit, Zeichenfolgendarstellungen, Identität. Ist da mehr? Gibt es weniger?

Unter der Annahme der Liste in 1), können Aufzählungen erweitert werden? Wenn das so ist, wie? (in demselben Paket? einem anderen Paket?) Wenn sie nicht erweitert werden können, warum nicht? Warum ist das in der Praxis kein Problem?

Ich glaube nicht, dass Aufzählungen aus zwei Gründen erweitert werden können:

  1. Aufzählungen sollten den gesamten Bereich akzeptabler Werte darstellen, daher ist eine Erweiterung nicht sinnvoll.
  2. Genauso wie normale Go-Typen nicht in externen Paketen erweitert werden können, behält dies die gleichen Mechanismen und Entwicklererwartungen bei

Namensraum: In Swift führt ein Enum-Typ einen neuen Namensraum ein. Es gibt eine bedeutende Maschinerie (syntaktischer Zucker, Typableitung), sodass der Namespace-Name nicht überall wiederholt werden muss. Beispielsweise kann man für Aufzählungswerte eines Aufzählungsmonats im richtigen Kontext .Januar anstelle von Monat.Januar schreiben (oder noch schlimmer, MyPackage.Month.January). Wird ein Enum-Namespace benötigt? Wenn ja, wie wird ein Enum-Namespace erweitert? Welche Art von syntaktischem Zucker ist erforderlich, damit dies in der Praxis funktioniert?

Ich verstehe, wie der Namespace zustande kam, da alle Beispiele, die ich erwähnt habe, mit dem Typnamen vorangestellt sind. Obwohl ich nichts dagegen hätte, wenn jemand unbedingt Namensräume hinzufügen würde, denke ich, dass dies für diesen Vorschlag nicht in Frage kommt. Das Präfix passt gut in das aktuelle System.

Sind Aufzählungswerte Konstanten? Unveränderliche Werte?

Ich würde an Konstanten denken.

Welche Operationen sind für Aufzählungswerte möglich (z. B. außer Iteration): Kann ich einen vorwärts und einen rückwärts verschieben? Sind zusätzliche integrierte Funktionen oder Operatoren erforderlich? (Möglicherweise sind nicht alle Iterationen in Ordnung). Was passiert, wenn man über den letzten Aufzählungswert hinausgeht? Ist das ein Laufzeitfehler?

Ich würde standardmäßig die Standard-Go-Praktiken für Slices/Arrays (keine Karten) verwenden. Enum-Werte wären basierend auf der Deklarationsreihenfolge iterierbar. Zumindest würde es Reichweitenunterstützung geben. Ich lehne mich davon ab, den Zugriff auf Enumerationen über den Index zuzulassen, fühle mich aber nicht stark dabei. Wenn Sie dies nicht unterstützen, sollte der potenzielle Laufzeitfehler beseitigt werden.

Es würde ein neuer Laufzeitfehler (Panik?) auftreten, der durch die Zuweisung eines ungültigen Werts zu einer Aufzählung verursacht wird, sei es durch direkte Zuweisung oder Typumwandlung.

Wenn ich das richtig zusammenfasse, dann sind Aufzählungswerte, wie Sie sie vorschlagen, wie typisierte Konstanten (und wie Konstanten können sie benutzerdefinierte Konstantenwerte haben), aber:

  • Sie definieren auch den Aufzählungstyp, der den Aufzählungswerten zugeordnet ist (ansonsten sind sie nur Konstanten), in derselben Deklaration
  • Es ist unmöglich, einen Aufzählungswert eines vorhandenen Aufzählungstyps außerhalb seiner Deklaration umzuwandeln/zu erstellen
  • Es ist möglich, über sie zu iterieren

Klingt das ungefähr richtig? (Dies würde dem klassischen Ansatz entsprechen, den Sprachen gegenüber Aufzählungen gewählt haben und der vor etwa 45 Jahren von Pascal entwickelt wurde).

Ja, genau das schlage ich vor.

Was ist mit switch-Anweisungen? AIUI, das ist einer der Haupttreiber für den Vorschlag.

Die Möglichkeit, eine Aufzählung einzuschalten, ist impliziert, denke ich, da Sie im Grunde alles einschalten können. Ich mag es, dass Swift Fehler hat, wenn Sie die Aufzählung in Ihrem Schalter nicht vollständig erfüllt haben, aber das könnte vom Tierarzt gehandhabt werden

@jediorange Ich bezog mich speziell auf die Frage des letzten Teils, ob es eine Vollständigkeitsprüfung geben sollte oder nicht (im Interesse der Vollständigkeit des Vorschlags). „Nein“ ist natürlich eine vollkommen gute Antwort.

Die ursprüngliche Botschaft dieser Ausgabe erwähnt Protobufs als Motivator. Ich möchte ausdrücklich darauf hinweisen, dass der Protobuf-Compiler mit der jetzt gegebenen Semantik einen zusätzlichen "nicht erkannten" Fall für jede Aufzählung erstellen müsste (was ein Namensverstümmelungsschema impliziert, um Kollisionen zu vermeiden). Es müsste auch ein zusätzliches Feld zu jeder generierten Struktur hinzufügen, die Aufzählungen verwendet (wiederum Namen auf irgendeine Weise verstümmelt), falls der decodierte Aufzählungswert nicht im einkompilierten Bereich liegt. So wie es derzeit für Java gemacht wird. Oder, wahrscheinlich wahrscheinlicher, verwenden Sie weiterhin int s.

@Merovius In meinem ursprünglichen Vorschlag wurden Protobufs als Beispiel erwähnt, nicht als Hauptmotivator für den Vorschlag. Sie sprechen einen guten Punkt über diese Integration an. Ich denke, es sollte wahrscheinlich als orthogonales Anliegen behandelt werden. Der meiste Code, den ich gesehen habe, konvertiert von den generierten Protobuf-Typen in Strukturen auf App-Ebene und zieht es vor, diese intern zu verwenden. Es würde für mich Sinn machen, dass protobuf unverändert fortgesetzt werden könnte, und wenn die App-Ersteller diese in eine Go-Enumeration konvertieren möchten, könnten sie die Grenzfälle behandeln, die Sie im Konvertierungsprozess ansprechen.

@derekperkins Noch ein paar Fragen:

  • Was ist der Nullwert für eine Variable vom Typ Enum, die nicht explizit initialisiert wird? Ich gehe davon aus, dass es im Allgemeinen nicht Null sein kann (was die Speicherzuweisung / -initialisierung erschwert).

  • Können wir begrenzte Arithmetik mit Aufzählungswerten durchführen? Beispielsweise musste in Pascal (in dem ich vor langer Zeit mal programmiert habe) überraschend oft in Schritten > 1 iteriert werden. Und manchmal wollte man den Enum-Wert berechnen.

  • In Bezug auf die Iteration, warum ist eine Go-Generate-Produced-Iteration- (und Stringify-) Unterstützung nicht gut genug?

Was ist der Nullwert für eine Variable vom Typ Enum, die nicht explizit initialisiert wird? Ich gehe davon aus, dass es im Allgemeinen nicht Null sein kann (was die Speicherzuweisung / -initialisierung erschwert).

Wie ich im ursprünglichen Vorschlag erwähnt habe, ist dies eine der schwierigeren Entscheidungen. Wenn die Definitionsreihenfolge für die Iteration von Bedeutung ist, wäre es meiner Meinung nach ebenfalls sinnvoll, den ersten definierten Wert als Standard festzulegen.

Können wir begrenzte Arithmetik mit Aufzählungswerten durchführen? Beispielsweise musste in Pascal (in dem ich vor langer Zeit mal programmiert habe) überraschend oft in Schritten > 1 iteriert werden. Und manchmal wollte man den Enum-Wert berechnen.

Unabhängig davon, ob Sie numerische oder stringbasierte Aufzählungen verwenden, bedeutet das, dass alle Aufzählungen einen impliziten nullbasierten Index haben? Der Grund, den ich zuvor erwähnt habe, dass ich nur zu unterstützten range -Iterationen neige und nicht indexbasiert, ist, dass die zugrunde liegende Implementierung nicht offengelegt wird, die ein Array oder eine Karte oder was auch immer darunter verwenden könnte. Ich gehe nicht davon aus, dass Sie über den Index auf Aufzählungen zugreifen müssen, aber wenn Sie Gründe haben, warum dies von Vorteil wäre, gibt es meines Erachtens keinen Grund, dies zu verbieten.

In Bezug auf die Iteration, warum ist eine Go-Generate-Produced-Iteration- (und Stringify-) Unterstützung nicht gut genug?

Iteration ist persönlich nicht mein Hauptanwendungsfall, obwohl ich denke, dass es dem Vorschlag einen Mehrwert verleiht. Wenn das der treibende Faktor wäre, würde vielleicht go generate ausreichen. Das trägt nicht zur Wertsicherheit bei. Das Argument Stringer() geht davon aus, dass der Rohwert iota oder int oder ein anderer Typ sein wird, der den "echten" Wert darstellt. Sie müssten auch (Un)MarshalJSON , (Un)MarshalBinary , Scanner/Valuer und alle anderen Serialisierungsmethoden generieren, die Sie möglicherweise verwenden, um sicherzustellen, dass der Stringer verwendet wurde, um vs was auch immer Go intern verwendet.

@griesemer Ich glaube, ich habe Ihre Frage zur Erweiterbarkeit von Aufzählungen möglicherweise nicht vollständig beantwortet, zumindest in Bezug auf das Hinzufügen/Entfernen von Werten. Die Fähigkeit, sie zu bearbeiten, ist ein wesentlicher Bestandteil dieses Vorschlags.

Von @Merovius https://github.com/golang/go/issues/19814#issuecomment -290969864

Jedes Paket, das jemals einen Satz von Enums ändern möchte, würde automatisch und zwangsweise alle seine Importer zerstören

Ich sehe nicht, wie sich dies von anderen API-Änderungen unterscheidet. Es ist Sache des Erstellers des Pakets, BC respektvoll zu behandeln, genauso wie wenn sich Typen, Funktionen oder Funktionssignaturen ändern.

Aus Sicht der Implementierung wäre es ziemlich komplex, Typen zu unterstützen, deren Standardwert nicht alle Bits-Null sind. Solche Typen gibt es heute nicht mehr. Die Forderung nach einem solchen Feature müsste als Zeichen gegen diese Idee gelten.

Der einzige Grund, warum die Sprache make zum Erstellen eines Kanals benötigt, ist, diese Funktion für Kanaltypen beizubehalten. Andernfalls könnte make optional sein und nur verwendet werden, um die Kanalpuffergröße festzulegen oder einer vorhandenen Variablen einen neuen Kanal zuzuweisen.

@derekperkins Die meisten anderen API-Änderungen können für eine schrittweise Reparatur orchestriert werden. Ich empfehle wirklich, die Beschreibung von Russ Cox zu lesen, sie macht viele Dinge sehr klar.

Offene Aufzählungen (wie das aktuelle const+iota-Konstrukt) ermöglichen eine schrittweise Reparatur, indem (zum Beispiel) a) der neue Wert definiert wird, ohne ihn zu verwenden, b) umgekehrte Abhängigkeiten aktualisiert werden, um den neuen Wert zu handhaben, c) mit der Verwendung des Werts begonnen wird. Oder, wenn Sie einen Wert entfernen möchten, a) beenden Sie die Verwendung des Werts, b) aktualisieren Sie umgekehrte Abhängigkeiten, um den zu entfernenden Wert nicht zu erwähnen, c) entfernen Sie den Wert.

Bei geschlossenen (vom Compiler auf Vollständigkeit geprüften) Aufzählungen ist dies nicht möglich. Wenn Sie die Behandlung eines Werts entfernen oder einen neuen definieren, wird sich der Compiler sofort über einen fehlenden Switch-Case beschweren. Und Sie können die Behandlung eines Werts nicht hinzufügen, bevor Sie einen definieren.

Die Frage ist nicht, ob die einzelnen Änderungen als brechend angesehen werden könnten (sie können isoliert betrachtet werden), sondern ob es eine nicht brechende Abfolge von Commits über die verteilte Codebasis gibt, die nicht brechen.

Aus Sicht der Implementierung wäre es ziemlich komplex, Typen zu unterstützen, deren Standardwert nicht alle Bits-Null sind. Solche Typen gibt es heute nicht mehr. Die Forderung nach einem solchen Feature müsste als Zeichen gegen diese Idee gelten.

@ianlancetaylor Ich werde definitiv nicht in der Lage sein, mit der vollständigen Implementierung zu sprechen, aber wenn Aufzählungen als 0-basiertes Array implementiert würden (was sich so anhört, als wäre @griesemer dafür), dann 0, wie der Index zu sein scheint es könnte als "Alle-Bits-Null" verdoppeln.

Bei geschlossenen (vom Compiler auf Vollständigkeit geprüften) Aufzählungen ist dies nicht möglich.

@Merovius Wenn die Vollständigkeit durch go vet oder ähnliche Tools überprüft würde, wie von @jediorange vorgeschlagen und vom Compiler erzwungen, würde das Ihre Bedenken zerstreuen?

@derekperkins Über ihre Schädlichkeit, ja. Nicht über ihren Mangel an Nützlichkeit. Die gleichen Probleme der Versionsabweichung treten auch bei den meisten Anwendungsfällen auf, für die sie normalerweise in Betracht gezogen werden (Systemaufrufe, Netzwerkprotokolle, Dateiformate, gemeinsam genutzte Objekte …). Es gibt einen Grund, warum proto3 offene Enums benötigt und proto2 nicht – es ist eine Lektion, die wir aus vielen Ausfällen und Datenkorruptionsvorfällen gelernt haben. Auch wenn Google bereits ziemlich vorsichtig ist, um Versionsverzerrungen zu vermeiden. Aus meiner Sicht sind offene Enumerationen mit Standardfällen genau die richtige Lösung. Und abgesehen von angeblicher Sicherheit gegen ungültige Werte bringen sie nicht wirklich viel auf den Tisch, soweit ich das beurteilen kann.

Alles in allem bin ich kein Entscheider.

@derekperkins In https://github.com/golang/go/issues/19814#issuecomment -322818206 bestätigen Sie Folgendes (aus Ihrer Sicht):

  • eine Enum-Deklaration deklariert einen Enum-Typ zusammen mit benannten Enum-Werten (Konstanten)
  • Es ist möglich, über sie zu iterieren
  • Außerhalb der Deklaration können der Aufzählung keine Werte hinzugefügt werden
    Und später: Eine Umstellung auf Enumerationen muss (oder darf es nicht sein) vollständig sein (scheint weniger wichtig zu sein)

In https://github.com/golang/go/issues/19814#issuecomment -322895247 sagen Sie Folgendes:

  • Der erste definierte Wert sollte wahrscheinlich der Standardwert (Null) sein (beachten Sie, dass dies für die Iteration keine Rolle spielt, es ist für die Initialisierung der Enum-Variable wichtig).
  • Iteration ist nicht Ihre primäre Motivation

Und in https://github.com/golang/go/issues/19814#issuecomment -322903714 sagen Sie, dass "die Möglichkeit, sie zu bearbeiten, ein wichtiger Teil dieses Vorschlags ist".

Ich bin verwirrt: Iteration ist also kein primärer Motivator, gut. Damit bleibt mindestens eine Enum-Deklaration von Enum-Werten, die Konstanten sind, und sie können nicht außerhalb der Deklaration erweitert werden. Aber jetzt sagen Sie, dass die Möglichkeit, sie zu bearbeiten, wichtig ist. Was bedeutet das? Sicherlich nicht, dass sie verlängert werden können (das wäre ein Widerspruch). Sind es Variablen? (Aber dann sind sie keine Konstanten).

In https://github.com/golang/go/issues/19814#issuecomment -322903714 sagen Sie, dass Enums als 0-basiertes Array implementiert werden könnten. Dies deutet darauf hin, dass eine Enum-Deklaration einen neuen Typ zusammen mit einer geordneten Liste von Enum-Namen einführt, die 0-basierte konstante Indizes in einem Array von Enum-Werten sind (für die automatisch Platz reserviert wird). Meinst Du das? Wenn ja, warum würde es nicht ausreichen, einfach ein Array fester Größe und eine Liste von Konstanten-Indizes dazu zu deklarieren? Die Überprüfung der Array-Grenzen würde automatisch sicherstellen, dass Sie den Enum-Bereich nicht "erweitern" können, und es wäre bereits eine Iteration möglich.

Was vermisse ich?

Ich bin verwirrt: Iteration ist also kein primärer Motivator, gut.

Ich habe meine eigenen Gründe, warum ich Aufzählungen möchte, während ich gleichzeitig versuche zu berücksichtigen, was andere in diesem Thread, einschließlich @bep und andere, als notwendige Teile des Vorschlags ausgedrückt haben.

Damit bleibt mindestens eine Enum-Deklaration von Enum-Werten, die Konstanten sind, und sie können nicht außerhalb der Deklaration erweitert werden. Aber jetzt sagen Sie, dass die Möglichkeit, sie zu bearbeiten, wichtig ist. Was bedeutet das? Sicherlich nicht, dass sie verlängert werden können (das wäre ein Widerspruch). Sind es Variablen? (Aber dann sind sie keine Konstanten).

Wenn ich sage, sie zu bearbeiten, geht es @Merovius darum , dass es sich um offene Aufzählungen handelt. Konstanten zur Build-Zeit, aber nicht für immer gesperrt.

In #19814 (Kommentar) sagen Sie, dass Enums als 0-basiertes Array implementiert werden könnten.

Dies ist nur meine Spekulation über meine Gehaltsstufe hinaus, wie ich mir vorstelle, dass es hinter den Kulissen implementiert werden könnte, basierend auf Ihrem https://github.com/golang/go/issues/19814#issuecomment -322884746 und @ianlancetaylors https: //github.com/golang/go/issues/19814#issuecomment -322899668

„Können wir mit Enum-Werten eingeschränkt rechnen? In Pascal (in dem ich vor langer Zeit einmal programmiert habe) war es zum Beispiel überraschend oft notwendig, in Schritten > 1 zu iterieren. Und manchmal wollte man den Enum-Wert berechnen.“

Ich weiß nicht, wie Sie das für eine nicht ganzzahlige Aufzählung planen würden, daher meine Frage, ob diese Arithmetik erfordern würde, dass jedem Mitglied der Aufzählung implizit ein Index basierend auf der Deklarationsreihenfolge zugewiesen wird.

Aus Sicht der Implementierung wäre es ziemlich komplex, Typen zu unterstützen, deren Standardwert nicht alle Bits-Null sind. Solche Typen gibt es heute nicht mehr. Die Forderung nach einem solchen Feature müsste als Zeichen gegen diese Idee gelten.

Auch hier weiß ich nicht, wie der Compiler funktioniert, also habe ich nur versucht, das Gespräch fortzusetzen. Am Ende des Tages versuche ich nicht, etwas Radikales vorzuschlagen. Wie Sie bereits erwähnt haben: "Dies würde dem klassischen Ansatz entsprechen, den Sprachen gegenüber Aufzählungen gewählt haben, der vor etwa 45 Jahren von Pascal entwickelt wurde", und das passt ins Bild.

Alle anderen, die Interesse bekundet haben, können sich gerne melden.

Eine andere Frage ist, ob man diese Aufzählungen verwenden kann, um in Arrays oder Slices zu indizieren. Ein Slice ist oft eine sehr effiziente und kompakte Möglichkeit, ein Enum->Wert-Mapping darzustellen, und das Erfordernis einer Map wäre unglücklich, denke ich.

@derekperkins Ok, ich mache mir Sorgen, das bringt uns (oder zumindest mich) wieder auf den Anfang: Was ist das Problem, das Sie zu lösen versuchen? Wollen Sie einfach eine schönere Art zu schreiben, was wir derzeit mit Konstanten und vielleicht Iota machen (und wofür wir go generate verwenden, um String-Repräsentationen zu erhalten)? Das heißt, etwas syntaktischer Zucker für eine Notation, die Sie (vielleicht) zu mühsam finden? (Das ist eine gute Antwort, ich versuche nur zu verstehen.)

Sie haben erwähnt, dass Sie Ihre eigenen Gründe haben, sie zu wollen, vielleicht können Sie ein bisschen mehr erklären, was diese Gründe sind. Das Beispiel, das Sie ganz am Anfang gegeben haben, ergibt für mich nicht viel Sinn, aber mir fehlt wahrscheinlich etwas.

Jeder hat derzeit ein etwas anderes Verständnis davon, was dieser Vorschlag ("Enums") beinhaltet, wie aus den verschiedenen Antworten deutlich geworden ist: Zwischen Pascal-Enums und Swift-Enumerationen gibt es eine große Bandbreite an Möglichkeiten. Wenn Sie (oder jemand anderes) nicht sehr klar beschreiben, was vorgeschlagen wird (ich bitte nicht um eine Implementierung, wohlgemerkt), wird es schwierig sein, nennenswerte Fortschritte zu erzielen oder auch nur die Vorzüge dieses Vorschlags zu diskutieren.

Ist das sinnvoll?

@griesemer Es macht absolut Sinn und ich verstehe die zu passierende Messlatte, über die @rsc auf der Gophercon gesprochen hat. Sie haben offensichtlich ein viel tieferes Verständnis, als ich es je haben werde. In #21473 haben Sie erwähnt, dass iota für vars nicht implementiert wurde, weil es zu diesem Zeitpunkt keinen überzeugenden Anwendungsfall gab. Ist das der gleiche Grund, warum enum nicht von Anfang an dabei war? Mich würde Ihre Meinung dazu interessieren, ob es einen Mehrwert für Go bringen würde oder nicht, und wenn ja, wo würden Sie mit dem Prozess beginnen?

@derekperkins Zu Ihrer Frage in https://github.com/golang/go/issues/19814#issuecomment -323144075: Zu der Zeit (in Gos Design) haben wir nur relativ einfache (z. B. Pascal- oder C-Stil) Aufzählungen in Betracht gezogen. Ich erinnere mich nicht an alle Details, aber es gab sicherlich das Gefühl, dass die zusätzliche Maschinerie, die für Aufzählungen erforderlich ist, nicht genug Nutzen bringt. Wir hatten das Gefühl, dass es sich im Wesentlichen um verherrlichte ständige Erklärungen handelte.

Es gibt auch Probleme mit diesen traditionellen Aufzählungen: Es ist möglich, mit ihnen zu rechnen (es sind nur ganze Zahlen), aber was bedeutet es, wenn sie "außerhalb des (Aufzählungs-)Bereichs" liegen? In Go sind sie nur Konstanten und "out of range" existiert nicht. Eine andere ist Iteration: In Pascal gab es spezielle eingebaute Funktionen (ich glaube SUCC und PRED), um den Wert einer Variablen vom Enum-Typ vorwärts und rückwärts zu erhöhen (in C macht man einfach ++ oder --). Aber auch hier taucht das gleiche Problem auf: Was passiert, wenn man über das Ende hinausgeht (sehr häufiges Problem in einer for-Schleife, die sich über Enum-Werte erstreckt, wenn ++ oder das Pascal-Äquivalent SUCC verwendet wird). Schließlich führt eine Enum-Deklaration einen neuen Typ ein, dessen Elemente die Enum-Werte sind. Diese Werte haben Namen (die in der Enum-Deklaration definierten), aber diese Namen befinden sich (in Pascal, C) im selben Bereich wie der Typ. Das ist etwas unbefriedigend: Wenn man zwei verschiedene Enums deklariert, würde man hoffen, dass man für jeden Enum-Typ denselben Enum-Wertnamen verwenden könnte, ohne dass Konflikte auftreten, was nicht möglich ist. Go löst das natürlich auch nicht, aber eine konstante Deklaration sieht auch nicht so aus, als würde sie einen neuen Namensraum einführen. Eine schönere Lösung besteht darin, bei jeder Aufzählung einen Namensraum einzuführen, aber dann muss jedes Mal, wenn ein Aufzählungswert verwendet wird, dieser mit dem Namen des Aufzählungstyps qualifiziert werden, was ärgerlich ist. Swift löst dies, indem der Aufzählungstyp nach Möglichkeit abgeleitet wird, und dann kann der Name des Aufzählungswerts mit einem vorangestellten Punkt verwendet werden. Aber das ist ziemlich viel Maschinerie. Und schließlich muss man manchmal (häufig in öffentlichen APIs) eine Enum-Deklaration erweitern. Wenn das nicht möglich ist (Sie besitzen den Code nicht), gibt es ein Problem. Bei Konstanten gibt es diese Probleme nicht.

Wahrscheinlich steckt noch mehr dahinter; das fällt mir gerade ein. Am Ende haben wir entschieden, dass es besser ist, Aufzählungen in Go mit den orthogonalen Werkzeugen zu emulieren, die wir bereits hatten: benutzerdefinierte Integer-Typen, die fehlerhafte Zuweisungen weniger wahrscheinlich machen, und den Iota-Mechanismus (und die Fähigkeit, wiederholte Initialisierungsausdrücke wegzulassen) für den syntaktischen Zucker.

Daher meine Fragen: Was möchten Sie von spezialisierten Enum-Deklarationen profitieren, die wir in Go mit wenig syntaktischem Overhead nicht angemessen emulieren können? Ich kann mir eine Aufzählung und einen Aufzählungstyp vorstellen, der nicht außerhalb der Deklaration erweitert werden kann. Ich kann mir mehr Fähigkeiten für Enum-Werte vorstellen, wie in Swift.

Die Aufzählung könnte einfach mit einem Go-Generator in Go gelöst werden. Wir haben bereits einen Stringer. Das Einschränken der Erweiterung ist über API-Grenzen hinweg problematisch. Mehr Fähigkeiten für Enum-Werte (z. B. in Swift) scheinen Go-unähnlich zu sein, da viele orthogonale Konzepte gemischt werden. In Go würden wir das wahrscheinlich mit elementaren Bausteinen erreichen.

@griesemer Danke für deine nachdenkliche Antwort. Ich widerspreche nicht, dass es sich im Grunde genommen um verherrlichte ständige Erklärungen handelt. Typsicherheit in Go zu haben ist großartig, und der wichtigste Wert, den enum mir persönlich bieten würde, ist Wertsicherheit. Die Möglichkeit, dies in Go heute nachzuahmen, besteht darin, Validierungsfunktionen an jedem Einstiegspunkt für diese Variable auszuführen. Es ist ausführlich und macht es leicht, Fehler zu machen, ist aber mit der heutigen Sprache möglich. Ich habe bereits einen Namensraum erstellt, indem ich den Typnamen vor der Aufzählung vorangestellt habe, was zwar ausführlich, aber keine große Sache ist.

Ich persönlich mag die meisten Verwendungen für iota nicht. Obwohl es cool ist, werden meine enum -ähnlichen Werte meistens externen Ressourcen wie einer Datenbank oder einer externen API zugeordnet, und ich ziehe es vor, deutlicher zu machen, dass der Wert nicht geändert werden sollte, wenn Sie zufällig neu bestellen. iota hilft auch nicht für die meisten Stellen, an denen ich enum verwenden würde, weil ich eine Liste von String-Werten verwenden würde.

Am Ende des Tages weiß ich nicht, wie viel mehr ich diesen Vorschlag verdeutlichen kann. Ich würde es lieben, wenn sie auf jede Art und Weise unterstützt würden, die für Go Sinn macht. Unabhängig von der genauen Implementierung könnte ich sie immer noch verwenden und sie würden meinen Code sicherer machen.

Ich denke, die kanonische Art und Weise, wie Go heute Aufzählungen durchführt (wie in https://github.com/golang/go/issues/19814#issuecomment-290909885 zu sehen), ist ziemlich genau richtig.
Es gibt ein paar Nachteile:

  1. Sie können nicht iteriert werden
  2. Sie haben keine Zeichenfolgendarstellung
  3. Clients können falsche Enum-Werte einführen

Mir geht es gut ohne Nr. 1.
go:generate + stringer kann für #2 verwendet werden. Wenn dies Ihren Anwendungsfall nicht bewältigt, machen Sie den Basistyp Ihrer "Enumeration" zu einem String statt einem Int und verwenden Sie String-Konstantenwerte.

3 ist mit dem heutigen Go schwer zu handhaben. Ich habe einen dummen Vorschlag, der damit gut umgehen könnte.

Fügen Sie einer Typdefinition ein Schlüsselwort explicit hinzu. Dieses Schlüsselwort verbietet Konvertierungen in diesen Typ, mit Ausnahme von Konvertierungen in konstanten Blöcken in dem Paket, in dem dieser Typ definiert ist. (Oder restricted ? Oder vielleicht bedeutet enum explicit type ?)

Wiederverwendung des Beispiels, auf das ich oben verwiesen habe,

//go:generate stringer -type=SearchRequest
explicit type SearchRequest int

const (
    Universal SearchRequest = iota
    Web
    Images
    Local
    News
    Products
    Video
)

Es gibt Umwandlungen von int in SearchRequest innerhalb des Blocks const . Aber nur der Paketautor kann einen neuen SearchRequest-Wert einführen, und es ist unwahrscheinlich, dass er versehentlich einen einführt (indem er zum Beispiel ein int an eine Funktion übergibt, die ein SearchRequest erwartet).

Ich schlage diese Lösung nicht wirklich aktiv vor, aber ich denke, dass das Nicht-aus Versehen-Konstruieren-eine-ungültige-eins die hervorstechende Eigenschaft von Aufzählungen ist, die heute in Go nicht erfasst werden können (es sei denn, Sie gehen die Struktur mit an nicht exportierte Feldroute).

Ich denke, das interessante Risiko bei Aufzählungen liegt bei nicht typisierten Konstanten. Leute, die eine explizite Typumwandlung schreiben, wissen, was sie tun. Ich wäre bereit, eine Möglichkeit für Go in Betracht zu ziehen, explizite Typkonvertierungen unter bestimmten Umständen zu verbieten, denke aber, dass dies völlig orthogonal zum Begriff der Aufzählungstypen ist. Es ist eine Idee, die für jede Art von Typ gilt.

Nicht typisierte Konstanten können jedoch dazu führen, dass versehentlich und unerwartet ein Wert des Typs erstellt wird, was bei expliziten Typkonvertierungen nicht der Fall ist. Ich denke also, dass der Vorschlag von @randall77 von explicit vereinfacht werden kann, um einfach zu bedeuten, dass nicht typisierte Konstanten möglicherweise nicht implizit in den Typ konvertiert werden. Eine explizite Typkonvertierung ist immer erforderlich.

Ich wäre bereit, eine Möglichkeit für Go in Betracht zu ziehen, explizite Typkonvertierungen unter bestimmten Umständen zu verbieten

@ianlancetaylor Das optionale Verbieten von Typkonvertierungen, ob explizit oder implizit, würde die Probleme lösen, die mich veranlasst haben, diesen Vorschlag überhaupt zu erstellen. Nur das Ursprungspaket wäre dann in der Lage, beliebige Typen zu erstellen und somit zu erfüllen. Das ist in mancher Hinsicht sogar besser als die enum -Lösung, weil es nicht nur const -Deklarationen unterstützt, sondern jeden Typ.

@randall77 , @ianlancetaylor Wie würde der explicit -Vorschlag mit dem Nullwert dieses Typs zusammenarbeiten?

@derekperkins Das vollständige Verbieten von Typkonvertierungen macht es unmöglich, diese Typen in generischen Encodern/Decodern wie den encoding/* -Paketen zu verwenden.

@Merovius Ich denke, @ianlancetaylor schlägt die Einschränkung nur für implizite Konvertierungen vor (z. B. Zuweisung einer nicht typisierten Konstante an einen eingeschränkten Typ). Eine explizite Konvertierung wäre weiterhin möglich.

@griesemer Ich weiß :) Aber ich habe @derekperkins verstanden, um es anders vorzuschlagen.

Untergräbt das Zulassen expliziter Konvertierungen nicht den eigentlichen Grund, warum wir über diesen "expliziten" Qualifizierer nachdenken? Wenn sich jemand entscheiden kann, einen beliebigen Wert in einen "expliziten" Typ umzuwandeln, haben wir keine Garantie mehr, dass ein gegebener Wert eine der aufgezählten Konstanten ist, als wir es jetzt tun.

Ich denke, es hilft bei der gelegentlichen oder unbeabsichtigten Verwendung von nicht typisierten Konstanten, was vielleicht das Wichtigste ist.

Ich nehme an, ich frage mich, ob das Verbieten expliziter Konvertierungen im "Geist von Go" ist. Das Verbieten expliziter Konvertierungen ist ein großer Schritt in Richtung einer Programmierung auf der Grundlage von Typen statt einer Programmierung auf der Grundlage des Schreibens von Code. Ich denke, Go bezieht eine klare Position für letzteres.

@griesemer @Merovius Ich werde das Zitat von @ianlancetaylor noch einmal posten, da es sein Vorschlag war, nicht meiner.

Ich wäre bereit, eine Möglichkeit für Go in Betracht zu ziehen, explizite Typkonvertierungen unter bestimmten Umständen zu verbieten

Sowohl @rogpeppe als auch @Merovius sprechen gute Punkte über die Auswirkungen an. Das Zulassen expliziter Konvertierungen, aber nicht impliziter Konvertierungen, löst das Problem der Garantie gültiger Typen nicht, aber der Verlust der generischen Codierung wäre ein ziemlich großer Nachteil.

Hier gab es viel hin und her, aber ich denke, es gab einige gute Ideen. Hier ist eine Zusammenfassung, was ich gerne sehen würde (oder etwas Ähnliches), die mit dem übereinzustimmen scheint, was einige andere gesagt haben. Ich gebe offen zu, dass ich kein Sprachdesigner oder Compiler-Programmierer bin, also weiß ich nicht, wie gut es funktionieren würde.

  1. Enums, die nur in Basistypen verwurzelt sind (string, uint, int, rune usw.). Wenn kein Basistyp erforderlich ist, könnte er standardmäßig uint?
  2. Alle gültigen Werte der Aufzählung müssen mit der Typdeklaration -- Constants deklariert werden. Ungültige (nicht in der Typdeklaration deklarierte) Werte können nicht in den Enum-Typ konvertiert werden.
  3. Automatische String-Darstellung zum Debuggen (nice to have).
  4. Kompilierzeit prüft auf Vollständigkeit in switch-Anweisungen für die Aufzählung. Empfehlen Sie optional (über go vet ?) einen default -Fall, auch wenn er bereits erschöpfend ist (wahrscheinlich ein Fehler) für zukünftige Änderungen.
  5. Der Nullwert sollte im Wesentlichen ungültig sein (nicht etwas in der Enum-Deklaration). Ich persönlich möchte, dass es nil ist, wie ein Stück.

Letzteres _kann_ ein bisschen kontrovers sein. Und ich weiß nicht genau, ob das funktionieren würde, aber ich denke, es würde semantisch passen -- ähnlich wie man nach einem nil -Slice suchen würde, könnte man Schecks für nil Aufzählungswert.

Was die Iteration betrifft, glaube ich nicht wirklich, dass ich sie jemals verwenden würde, aber ich sehe keinen Schaden darin.

Als Beispiel, wie es deklariert werden könnte:

type MetadataBlockType enum[uint] {
    StreamInfo:    0
    Padding:       1
    Application:   2
    SeekTable:     3
    VorbisComment: 4
    CueSheet:      5
    Picture:       6
}

Außerdem wäre der Swift-Stil, den Typ abzuleiten und "Punktsyntax" zu verwenden, _nett_, aber definitiv nicht notwendig.

Typ EnumA int
konstant (
Unbekannt EnumA = iota
AAA
)


Geben Sie EnumB int ein
konstant (
Unbekannt EnumB = iota
BBB
)

Es können nicht 2 Codeteile in einer einzigen Go-Datei oder demselben Paket vorhanden sein, noch wird einer aus einem anderen Paket importiert.

Bitte implementieren Sie einfach die C#-Methode zur Implementierung von Enum:
type Days enum {Sa, So, Mo, Di, Mi, Do, Fr}
type Days enum[int] { Sa:1 , So, Di, Mi, Do, Fr}
type Days enum[string] { Sa: "Samstag" , So: "Sonntag" etc}

@KamyarM Wie ist das besser als

type Days int
const (
  Sat Days = 1+iota
  Sun
  ...
)

und

type Days string
const (
  Sat Days = "Saturday"
  Sun      = "Sunday"
  ...
)

Ich möchte freundlich darum bitten, Kommentare auf neue Ansätze/Argumente zu beschränken. Viele Leute haben diesen Thread abonniert und das Hinzufügen von Rauschen/Wiederholungen kann als respektlos gegenüber ihrer Zeit und Aufmerksamkeit empfunden werden. Dort oben gibt es viele Diskussionen ↑, einschließlich detaillierter Antworten auf die beiden vorherigen Kommentare. Sie werden nicht mit allem einverstanden sein, was gesagt wurde, und keine der Seiten mag die Ergebnisse dieser Diskussion bisher mögen - aber sie einfach zu ignorieren, wird auch nicht helfen, sie in eine produktive Richtung zu bringen.

Es ist besser, weil es kein Problem mit Namenskonflikten gibt. Unterstützt auch die Überprüfung des Compilertyps. Der von Ihnen erwähnte Ansatz hat es besser als nichts organisiert, aber der Compiler beschränkt Sie nicht darauf, was Sie ihm zuweisen können. Sie können einem Objekt dieses Typs eine Ganzzahl zuweisen, die keiner der Tage ist:
var a Tage
a = 10
Compiler tut eigentlich nichts dagegen. Diese Art von Aufzählung hat also nicht viel Sinn. abgesehen davon, dass es in IDEs wie GoLand besser organisiert ist

So etwas würde ich gerne sehen

type WeekDay enum string {
  Monday "mon"
  Tuesday "tue"
  // etc...
}

Oder mit automatischer iota Nutzung:

// this assumes that iota automatically assigned to the first declared enum key
// and values have unsigned integer type
// value is positional, so if you decide to rename your key 
// you don't have to change everything in db
// also it is easy to grow your lists
type WeekDay enum {
  Monday
  Tuesday
}

Dies sorgt für Einfachheit und Benutzerfreundlichkeit:

func makeItWorkOn(day WeekDay) {
  // your implementation
}

Außerdem sollte enum eine eingebaute Methode zum Validieren des Werts haben, damit wir etwas aus der Benutzereingabe validieren können:

if day in WeekDay {
  makeItWorkOn(day)
}

Und einfache Dinge wie:

if day == WeekDay.Monday {
 // whatever
}

Um ehrlich zu sein, wäre meine Lieblingssyntax so (KISS):

// type automatically inferred from values or `iota`
enum WeekDay {
  Monday "mon"
  Tuesday "tue"
}

@zoonman Das letzte Beispiel folgt nicht dem folgenden Go-Prinzip: Eine Funktionsdeklaration beginnt mit func , eine Typdeklaration beginnt mit type , eine Variablendeklaration beginnt mit var , ...

@md2perpe Ich versuche nicht, den "Typ"-Prinzipien von Go zu folgen, ich schreibe jeden Tag Code und das einzige Prinzip, dem ich folge, ist, die Dinge einfach zu halten.
Als mehr Code, den Sie schreiben müssen, um Prinzipien zu folgen, wird mehr Zeit verschwendet.
TBH Ich bin Go-Neuling, aber es gibt eine Menge Dinge, die ich kritisieren kann.
Zum Beispiel:

struct User {
  Id uint
  Email string
}

Ist einfacher zu schreiben und zu verstehen als

type User struct {
  Id uint
  Email string
}

Ich kann Ihnen ein Beispiel geben, wo Typ verwendet werden sollte:

// this is terrible because it blows your mind off
// especially if you Go newbie
// this should not be allowed by compiler/linter:
w := map[string]interface{}{"type": 0, "connected": true}

// it has to be splitted into type definition
type WeirdJSON map[string]interface{}

// and used like
w := WeirdJSON{"type": 0, "connected": true}

Früher habe ich Code in Asm, C, C++, Pascal, Perl, PHP, Ruby, Python, JavaScript, TypeScript geschrieben, jetzt Go. Ich habe das alles gesehen. Diese Erfahrung sagt mir, dass Code lakonisch, leicht lesbar und verständlich sein muss .

Ich mache ein maschinelles Lernprojekt und muss eine MIDI-Datei analysieren.
Dort muss ich den SMPTE-Timecode analysieren. Ich finde es ziemlich schwierig, idiomatische Weise mit iota zu verwenden, aber es hält mich nicht davon ab)

const (
        SMTPE0 int8 = ((-24 - (1 + (iota - 1) * 3) % 6 * (iota - 1) / ((iota - 1) | 0x01)) - 10 * ((iota - 1) % 2) - 5 * (iota / 3 - iota / 4) ) * iota / (iota | 0x01)
    SMTPE24 
    SMTPE25
    SMTPE29
    SMTPE30
)
const (
   _SMTPE0 int8 = 0 
   _SMTPE24 int8 = -24
   _SMTPE25 int8 = -25
   _SMTPE29 int8 = -29
   _SMTPE30 int8 = -30
)

Natürlich brauche ich vielleicht eine Laufzeitprüfung mit defensiver Programmierung ...

func IsSMTPE(status int8) bool {
    j := 4
    for i:= 0; i >= -30; i -= j % 6{
        if i == int(status){ 
            return true
        }
        j+=3
    }

    return status == 0
}

PlayGroundRef

Enums machen Programmierern das Leben in manchen Fällen einfacher. Enums ist nur ein Instrument, wenn Sie es richtig verwenden, kann es Zeit sparen und die Produktivität steigern. Ich denke, es gibt keine Probleme, dies in Go 2 wie in C++, C# oder anderen Sprachen zu implementieren. Dieses Beispiel ist nur ein Witz, aber es zeigt deutlich das Problem.

@ streeter12 Ich sehe nicht, wie Ihr Beispiel "das Problem deutlich zeigt". Wie würden Aufzählungen diesen Code besser oder sicherer machen?

Es gibt eine C#-Klasse mit Implementierung der gleichen Logik von enum.

 public enum SMTPE : sbyte
   {
        SMTPE0 = 0,
        SMTPE24 = -24,
        SMTPE25 = -25,
        SMTPE29 = -29,
        SMTPE30 = -30
   }

   public class TestClass
   {
        public readonly SMTPE smtpe;

        public TestClass(SMTPE smtpe)
        {
            this.smtpe = smtpe;
        }
   } 

Mit Compile-Time-Enumerationen kann ich:

  1. Haben Sie keine Laufzeitprüfungen.
  2. Signifikante Verringerung der Fehlerwahrscheinlichkeit durch das Team (Sie können in der Kompilierzeit keinen falschen Wert übergeben).
  3. Es widerspricht nicht dem Iota-Konzept.
  4. Leichter zu verstehende Logik, als Sie einen Namen für Konstanten haben (es ist wichtig, dass Konstanten einige Protokollwerte auf niedriger Ebene darstellen).
  5. Sie können die ToString()-Methode analog machen, um eine einfache Darstellung von Werten zu erhalten. (CONNECTION_ERROR.NO_INTERNET ist besser als 0x12). Ich kenne mich mit Stringer aus, aber es gibt keine explizite Codegenerierung mit Aufzählungen.
  6. In einigen Sprachen können Sie ein Array von Werten, Bereichen usw. erhalten.
  7. Es ist beim Lesen des Codes einfach zu verstehen (keine Berechnungen im Kopf erforderlich).

Schließlich ist es nur ein Werkzeug, um einige häufige menschliche Fehler zu vermeiden und Leistung zu sparen.

@streeter12 Danke für die Klarstellung, was du meinst. Der einzige Vorteil gegenüber Go-Konstanten ist hier, dass man keinen ungültigen Wert einführen kann, da das Typsystem keinen anderen Wert als einen der Enum-Werte akzeptiert. Das ist sicherlich schön zu haben, hat aber auch seinen Preis: Es gibt keine Möglichkeit, diese Aufzählung außerhalb dieses Codes zu erweitern. Die externe Aufzählungserweiterung ist einer der Hauptgründe, warum wir uns in Go gegen Standard-Aufzählungen entschieden haben.

Antworten Sie einfach darauf, dass einige Erweiterungen keine Aufzählungen verwenden.
FE muss Zustandsmaschinen dazu bringen, Zustandsmuster anstelle von Aufzählungen zu verwenden.

Aufzählungen haben ihren eigenen Geltungsbereich. Ich schließe einige große Projekte ohne Enum ab. Ich denke, es ist eine schreckliche Architekturentscheidung, Enum außerhalb des Definitionscodes zu erweitern. Sie haben keine Kontrolle darüber, was Ihr Kollege tut und es macht einige lustige Fehler)

Und Sie haben vergessen, dass Human Factor Enums in vielen Fällen Fehler in großen Projekten erheblich reduzieren.

@streeter12 Leider müssen Aufzählungen oft erweitert werden.

@griesemer Erweitern eines Aufzählungs-/Summentyps erstellt einen separaten und manchmal inkompatiblen Typ.

Dies gilt immer noch für Go, obwohl es keine expliziten Typen für Aufzählungen/Summen gibt. Wenn Sie einen "enum type" in einem Paket haben, das Werte in {1, 2, 3} erwartet, und Sie ihm eine 4 von Ihrem "erweiterten enum type" übergeben, haben Sie immer noch den Vertrag des impliziten "type" verletzt.

Wenn Sie eine Aufzählung/Summe erweitern müssen, müssen Sie auch explizite To/From-Konvertierungsfunktionen erstellen, die die manchmal inkompatiblen Fälle explizit behandeln.

Ich denke, die Trennung zwischen diesem Argument und den Leuten für diesen Vorschlag oder ähnliche Vorschläge wie #19412 ist, dass wir es seltsam finden, dass der Kompromiss darin besteht, „immer grundlegenden Validierungscode zu schreiben, den der Compiler verarbeiten kann“, anstatt „manchmal Konvertierungsfunktionen zu schreiben, die Sie“ werd wohl sowieso schreiben müssen".

Das soll nicht heißen, dass eine Seite Recht oder Unrecht hat oder dass dies der einzige Kompromiss ist, den es zu berücksichtigen gilt, aber ich wollte einen Engpass in der Kommunikation zwischen den Seiten identifizieren, den ich bemerkt habe.

Ich denke, die Trennung zwischen diesem Argument und den Leuten für diesen Vorschlag oder ähnliche Vorschläge wie #19412 ist, dass wir es seltsam finden, dass der Kompromiss darin besteht, „immer grundlegenden Validierungscode zu schreiben, den der Compiler verarbeiten kann“, anstatt „manchmal Konvertierungsfunktionen zu schreiben, die Sie“ werd wohl sowieso schreiben müssen".

Sehr gut angegeben

@jimmyfrasche So würde ich persönlich den Kompromiss nicht beschreiben. Ich würde sagen, es ist "immer grundlegenden Validierungscode schreiben, den der Compiler verarbeiten kann" vs. "dem Typsystem ein völlig neues Konzept hinzufügen, das jeder, der Go verwendet, lernen und verstehen muss."

Oder lassen Sie es mich anders ausdrücken. Soweit ich das beurteilen kann, sind die einzigen wesentlichen Merkmale, die in Gos Version der Aufzählungstypen fehlen, dass es keine Validierung der Zuweisung von nicht typisierten Konstanten gibt, es keine Prüfung auf explizite Konvertierungen gibt und es keine Prüfung gibt, dass alle Werte in a behandelt wurden schalten. Mir scheint, dass diese Merkmale alle unabhängig von der Vorstellung von Aufzählungstypen sind. Wir sollten uns nicht von der Tatsache, dass andere Sprachen Aufzählungstypen haben, zu dem Schluss führen lassen, dass Go auch Aufzählungstypen benötigt. Ja, Aufzählungstypen würden uns diese fehlenden Funktionen geben. Aber ist es wirklich notwendig, eine völlig neue Art von Typ hinzuzufügen, um sie zu erhalten? Und ist die Zunahme der Sprachkomplexität die Vorteile wert?

@ianlancetaylor Das Hinzufügen von Komplexität zur Sprache ist sicherlich eine zu berücksichtigende Sache, und "weil eine andere Sprache sie hat" ist sicherlich kein Argument. Ich persönlich glaube nicht, dass Enum-Typen es alleine wert sind. (Ihre Verallgemeinerung, Summentypen, kreuzen jedoch sicher viele Kästchen für mich an).

Eine allgemeine Möglichkeit für einen Typ, sich von der Zuweisbarkeit abzumelden, wäre schön, obwohl ich nicht sicher bin, wie weitreichend das außerhalb von Primitiven nützlich wäre.

Ich bin mir nicht sicher, wie verallgemeinerbar das Konzept "alle in einem Schalter behandelten Werte überprüfen" ist, ohne den Compiler auf irgendeine Weise über die vollständige Liste der zulässigen Werte zu informieren. Abgesehen von Aufzählungs- und Summentypen fällt mir nur etwas wie die Bereichstypen von Ada ein, aber diese sind natürlich nicht mit Nullwerten kompatibel, es sei denn, 0 muss im Bereich sein oder Code wird generiert, um Offsets zu verarbeiten, wenn sie konvertiert oder reflektiert werden auf. (Andere Sprachen hatten ähnliche Typenfamilien, einige in der Pascal-Familie, aber Ada ist die einzige, die mir im Moment einfällt.)

Jedenfalls meinte ich konkret:

Der einzige Vorteil gegenüber Go-Konstanten ist hier, dass man keinen ungültigen Wert einführen kann, da das Typsystem keinen anderen Wert als einen der Enum-Werte akzeptiert. Das ist sicherlich schön zu haben, hat aber auch seinen Preis: Es gibt keine Möglichkeit, diese Aufzählung außerhalb dieses Codes zu erweitern. Die externe Aufzählungserweiterung ist einer der Hauptgründe, warum wir uns in Go gegen Standard-Aufzählungen entschieden haben.

und

Leider müssen Aufzählungen in der Realität oft erweitert werden.

Dieses Argument funktioniert für mich aus den von mir genannten Gründen nicht.

@jimmyfrasche Verstanden; Es ist ein schwieriges Problem. Aus diesem Grund haben wir in Go nicht versucht, es zu lösen, sondern stattdessen nur einen Mechanismus bereitgestellt, um auf einfache Weise Sequenzen von Konstanten zu erstellen, ohne dass der konstante Wert wiederholt werden muss.

(Verspätet versendet - war als Antwort auf https://github.com/golang/go/issues/19814#issuecomment-349158748 gedacht)

@griesemer in der Tat und es war definitiv der richtige Anruf für Go 1, aber einiges davon ist es wert, für Go 2 neu bewertet zu werden.

Es gibt genug in der Sprache, um _fast_ alles herauszuholen, was man sich von Aufzählungstypen wünscht. Es erfordert mehr Code als eine Typdefinition, aber ein Generator könnte das meiste davon verarbeiten, und Sie können so viel oder so wenig definieren, wie es der Situation entspricht, anstatt nur die Befugnisse zu erhalten, die mit einem Aufzählungstyp verbunden sind.

Dieser Ansatz https://play.golang.org/p/7ud_3lrGfx bringt Ihnen alles außer

  1. Sicherheit innerhalb des definierenden Pakets
  2. die Fähigkeit, einen Schalter der Vollständigkeit halber zu fusseln

Dieser Ansatz kann auch für kleine, einfache Summentypen verwendet werden†, ist aber umständlicher zu verwenden, weshalb ich denke, dass etwas wie https://github.com/golang/go/issues/19412#issuecomment -323208336 dazu beitragen würde die Sprache und könnte von einem Codegenerator verwendet werden, um Aufzählungstypen zu erstellen, die die Probleme 1 und 2 vermeiden.

† siehe https://play.golang.org/p/YFffpsvx5e für eine Skizze von json.Token mit dieser Konstruktion

Wir finden es seltsam, dass der Kompromiss lautet: „Immer grundlegenden Validierungscode schreiben, den der Compiler verarbeiten kann“ statt „Manchmal Konvertierungsfunktionen schreiben, die Sie wahrscheinlich sowieso auch schreiben müssen“.

Für mich – einen Vertreter des Lagers erbitterter Befürworter der schrittweisen Reparatur – scheint dies der (etwas) richtige Kompromiss zu sein. Ehrlich gesagt, auch wenn wir nicht von einer schrittweisen Reparatur sprechen, würde ich das als besseres mentales Modell betrachten.

Zum einen kann typgeprüftes Enum ohnehin nur in den Quellcode eingefügte Werte überprüfen. Wenn die Aufzählung über ein Netzwerk wandert, auf der Festplatte gespeichert oder zwischen Prozessen ausgetauscht wird, ist alles möglich (und die meisten vorgeschlagenen Verwendungen von Aufzählungen fallen in diese Kategorie). Um das Problem der Handhabung von Inkompatibilitäten zur Laufzeit kommt man also sowieso nicht herum. Und es gibt kein allgemeines Standardverhalten, das für alle gilt, wenn Sie auf einen ungültigen Aufzählungswert stoßen. Oft möchten Sie vielleicht einen Fehler machen. Manchmal möchten Sie es vielleicht in einen Standardwert zwingen. Meistens möchten Sie es aufbewahren und weitergeben, damit es bei einer erneuten Serialisierung nicht verloren geht.

Natürlich könnten Sie argumentieren, dass es immer noch eine Vertrauensgrenze geben sollte, an der die Gültigkeit überprüft und das erforderliche Verhalten implementiert wird - und alles innerhalb dieser Grenze sollte diesem Verhalten vertrauen können. Und das mentale Modell scheint zu sein, dass diese Vertrauensgrenze ein Prozess sein sollte. Weil der gesamte Code in einer Binärdatei atomar geändert wird und intern konsistent bleibt. Aber dieses mentale Modell wird durch die Idee einer allmählichen Reparatur untergraben; Plötzlich wird die natürliche Vertrauensgrenze zu einem Paket (oder vielleicht einem Repository), da die Einheiten, auf die Sie Ihre atomaren Reparaturen anwenden, und die Einheit, der Sie vertrauen, selbstkonsistent sind.

Und ich persönlich finde das eine sehr natürliche und großartige Einheit der Selbstkonsistenz. Ein Paket sollte gerade groß genug sein, um seine Semantik, Regeln und Konventionen im Kopf zu behalten. Aus diesem Grund funktionieren Exporte auch auf Paketebene, nicht auf Typebene, und Deklarationen auf oberster Ebene werden auf Paketebene und nicht auf Programmebene erfasst. Scheint mir gut und sicher genug zu sein, um auch auf Paketebene über die korrekte Behandlung unbekannter Enum-Werte zu entscheiden. Haben Sie eine nicht exportierte Funktion, die es überprüft und das intern gewünschte Verhalten beibehält.

Ich wäre viel mehr an Bord mit einem Vorschlag, dass jeder Schalter einen Standardfall benötigt, als mit einem Vorschlag, typgeprüfte Aufzählungen einschließlich Vollständigkeitsprüfungen zu haben.

@Merovius Der Betriebssystemprozess und das Paket sind beide Vertrauensgrenzen, wie Sie es ausdrücken.

Informationen, die von außerhalb des Prozesses kommen, müssen bei ihrem Eingang validiert und in eine angemessene Darstellung für den Prozess entpackt werden, und angemessene Sorgfalt muss angewendet werden, wenn dies fehlschlägt. Das geht nie weg. Ich sehe dort nicht wirklich etwas Spezifisches für Sum/Enum-Typen. Dasselbe könnten Sie über Strukturen sagen – manchmal erhalten Sie zusätzliche Felder oder zu wenige Felder. Strukturen sind immer noch nützlich.

Allerdings können Sie mit Enum Type natürlich Fälle einbeziehen, die für die Modellierung dieser Fehler spezifisch sind. Zum Beispiel

type FromTheNetwork enum {
  // pretend all the "valid" values are listed here
  Missing // the value was not included in the message
  Unknown // the value was not in the set of the valid values
  Error // there was an error attempting to read the value
}

und mit Summentypen können Sie noch weiter gehen:

type FromTheNetwork pick {
  Missing struct{} // Not included in message
  Valid somepkg.TheSumBeingReceived // Include valid states with composition
  Unknown []byte // Raw bytes of unknown value received
  Error error // The error from attempting to read the value
}

(Ersteres ist nicht so nützlich, es sei denn, es wird in einer Struktur mit Feldern gespeichert, die für die Fehlerfälle spezifisch sind, aber dann hängt die Gültigkeit dieser Felder vom Wert der Aufzählung ab. Der Summentyp kümmert sich darum, da es im Wesentlichen a ist Struktur, die jeweils nur einen Feldsatz haben kann.)

Auf Paketebene müssen Sie immer noch die Validierung auf hoher Ebene durchführen, aber die Validierung auf niedriger Ebene wird mit dem Typ geliefert. Ich würde sagen, das Reduzieren der Domäne des Typs hilft, das Paket klein und in Ihrem Kopf zu halten. Es macht auch die Absicht für die Werkzeuge klarer, sodass Ihr Editor alle case X: -Zeilen ausschreiben und Sie den eigentlichen Code ausfüllen lassen könnte, oder ein Linter könnte verwendet werden, um sicherzustellen, dass der gesamte Code alle Fälle überprüft (Sie hat mich davon abgehalten, die Vollständigkeit im Compiler früher zu haben).

Ich sehe dort nicht wirklich etwas Spezifisches für Sum/Enum-Typen. Dasselbe könnten Sie über Strukturen sagen – manchmal erhalten Sie zusätzliche Felder oder zu wenige Felder. Strukturen sind immer noch nützlich.

Wenn wir über offene Aufzählungen sprechen (wie die, die derzeit von iota erstellt werden), dann sicher. Wenn wir über geschlossene Aufzählungen sprechen (was die Leute normalerweise sagen, wenn sie über Aufzählungen sprechen) oder Aufzählungen mit Vollständigkeitsprüfung, dann sind sie sicherlich etwas Besonderes. Denn sie sind nicht erweiterbar.

Die Analogie mit Strukturen erklärt dies ziemlich perfekt: Das Go 1-Kompatibilitätsversprechen schließt nicht verschlüsselte Strukturliterale von allen Versprechen aus - und daher wurde die Verwendung von verschlüsselten Strukturliteralen als so stark als "beste" Praxis angesehen, dass go vet eine Prüfung darauf hat. Der Grund ist genau derselbe: Wenn Sie ungeschlüsselte Struct-Literale verwenden, sind Structs nicht mehr erweiterbar.

Also ja. Strukturen sind in dieser Hinsicht genau wie Aufzählungen. Und wir haben uns als Community darauf geeinigt, dass es vorzuziehen ist, sie auf erweiterbare Weise zu verwenden.

Allerdings können Sie mit Enum Type natürlich Fälle einbeziehen, die für die Modellierung dieser Fehler spezifisch sind.

Ihr Beispiel deckt nur die Prozessgrenze ab (indem Sie über Netzwerkfehler sprechen), nicht die Paketgrenze. Wie werden sich Pakete verhalten, wenn ich einen "InvalidInternalState" (um etwas zu erfinden) zu FromTheNetwork hinzufüge? Muss ich ihre Schalter reparieren, bevor sie erneut kompilieren? Dann ist es im schrittweisen Reparaturmodell nicht erweiterbar. Benötigen sie einen Standardfall, um überhaupt zu kompilieren? Dann scheinen Aufzählungen keinen Sinn zu machen.

Auch hier ist es eine andere Frage, offene Aufzählungen zu haben. Ich wäre bei Dingen wie an Bord

Ich würde sagen, das Reduzieren der Domäne des Typs hilft, das Paket klein und in Ihrem Kopf zu halten. Es macht auch die Absicht für das Tooling klarer, sodass Ihr Editor alle Zeilen für den Fall X: ausschreiben und Sie den eigentlichen Code ausfüllen kann, oder ein Linter könnte verwendet werden, um sicherzustellen, dass der gesamte Code alle Fälle überprüft

Aber dafür brauchen wir keine tatsächlichen Enums als Typen. Ein solches Linting-Tool könnte auch mithilfe von iota heuristisch nach const -Deklarationen suchen, wobei jeder Fall von einem bestimmten Typ ist, und dies als "eine Aufzählung" betrachten und die gewünschten Überprüfungen durchführen. Ich wäre voll und ganz an Bord mit einem Tool, das diese "Aufzählungen nach Konvention" verwendet, um die automatische Vervollständigung oder das Linting zu unterstützen, dass jeder Schalter einen Standardwert haben muss oder dass sogar jeder (bekannte) Fall überprüft werden muss. Ich hätte sogar nichts dagegen, ein Enum-Keyword hinzuzufügen, das sich meistens so verhält; Das heißt, eine Aufzählung ist offen (kann jeden ganzzahligen Wert annehmen), gibt Ihnen den zusätzlichen Bereich und erfordert, dass in jedem Schalter ein Standardwert vorhanden ist (ich glaube nicht, dass sie für die zusätzlichen Kosten genug über Iota-Aufzählungen hinzufügen würden, aber zumindest sie würden meiner Agenda nicht schaden ). Wenn das vorgeschlagen wird - gut. Aber es scheint nicht das zu sein, was die Mehrheit der Befürworter dieses Vorschlags (sicherlich nicht des ursprünglichen Textes) meint.

Wir können uns nicht darüber einig sein, wie wichtig es ist, eine schrittweise Reparatur und Erweiterbarkeit möglich zu halten – zum Beispiel glauben viele Leute, dass die semantische Versionierung eine bessere Lösung für die Probleme ist, die sie löst. Aber wenn Sie sie für wichtig halten, ist es vollkommen berechtigt und vernünftig, Aufzählungen entweder als schädlich oder als sinnlos anzusehen. Und das war die Frage, auf die ich geantwortet habe: Wie können Leute vernünftigerweise den Kompromiss eingehen, überall eine Prüfung zu verlangen, anstatt sie im Compiler zu haben. Antwort: Indem man die Erweiterbarkeit und Weiterentwicklung von APIs wertschätzt, was diese Überprüfungen ohnehin am Einsatzort notwendig macht.

Von Zeit zu Zeit sagten Gegner von Enum, dass sie nicht erweiterbar sind, wir brauchen immer noch Überprüfungen nach der Serialisierung/Übergang, wir können die Kompatibilität zurücksetzen usw.

Das Hauptproblem, dass dies keine Enum-Probleme sind, sind Ihre Entwicklungs- und Artitecture-Probleme.
Sie versuchen, ein Beispiel zu geben, wo die Verwendung von Enums lächerlich ist, aber lassen Sie uns einige Situationen genauer betrachten.

Beispiel 1. Ich bin Low-Level-Entwickler und brauche const für einige Registeradressen, etablierte Low-Level-Protokollwerte usw. Im Moment habe ich in Go nur eine Lösung: Consts ohne Iota zu verwenden, da dies in vielen Fällen hässlich wäre . Ich kann mehrere Konstantenblöcke für ein Paket erhalten und nach dem Drücken von . Ich habe alle 20 Konstanten und wenn sie den gleichen Typ und ähnliche Namen haben, kann ich Fehler machen. Wenn das Projekt groß ist, erhalten Sie diesen Fehler. Um dies mit defensiver Programmierung zu verhindern, müssen wir bei TDD Duplicate Check Code (Duplicate Code = Duplicate Errors/Tests in jedem Fall) angreifen. Bei der Verwendung von Transfers haben wir keine ähnlichen Probleme und die Werte werden sich in diesem Fall nie ändern (versuchen Sie, eine Situation zu finden, in der Registeradressen in der Produktion geändert werden :)). Wir überprüfen immer noch manchmal, ob der Wert, den wir von Datei/Netz usw. erhalten, im Bereich liegt, aber es gibt keine Probleme, dies zu zentrieren (siehe c# Enum.TryParsezum Beispiel). Mit Enums spare ich in diesem Fall Entwicklungszeit und Performance.

Beispiel 2. Ich entwickle ein kleines Modul mit Zustands-/Fehlerlogik. Wenn ich Enum privat mache, weiß niemand jemals von diesem Enum, und Sie können es ohne Probleme mit allen Vorteilen von 1 ändern/erweitern. Wenn Sie Ihren Code auf einer privaten Logik basieren, ist bei Ihrer Entwicklung etwas völlig schief gelaufen.

Beispiel 3. Ich entwickle häufig geänderte und erweiterbare Module für eine Vielzahl von Anwendungen. Es ist eine seltsame Lösung, Aufzählungen oder andere Konstanten zu verwenden, um die öffentliche Logik/Schnittstelle zu bestimmen. Wenn Sie der Client-Server-Architektur eine neue Enum-Nummer hinzufügen, können Sie abstürzen, aber mit Konstanten können Sie einen unvorhersehbaren Zustand des Modells erhalten und ihn sogar auf der Festplatte speichern. Ich bevorzuge einen Absturz gegenüber einem unvorhersehbaren Zustand. Dies zeigt uns, dass das Problem der Rückkopierbarkeit/Erweiterung ein Problem unserer Entwicklungen und nicht der Enums ist. Wenn Sie verstehen, welche Aufzählungen in diesem Fall nicht geeignet sind, verwenden Sie sie einfach nicht. Ich denke, wir haben genug Entscheidungskompetenz.

Der Hauptunterschied zwischen consts und Compile Time Enum besteht meiner Meinung nach darin, dass Enums zwei Hauptverträge hat.

  1. Namensvertrag.
  2. Wertvertrag.
    Alle Argumente für und gegen diesen Absatz wurden bereits berücksichtigt.
    Wenn Sie Vertragsprogrammierung verwenden, können Sie die Vorteile leicht verstehen.

Enums wie viele andere Dinge haben ihre Nachteile.
Bspw. ohne Bremsfähigkeit nicht änderbar. Aber wenn Sie O aus SOLID-Prinzipien kennen, gilt dies nicht nur für Enum, sondern für die Entwicklung im Allgemeinen. Jemand kann sagen, ich mache mein Programm mit paralleler Logik und veränderlichen Strukturen hässlich. Lassen Sie uns veränderliche Strukturen verbieten? Stattdessen können wir veränderliche/nicht veränderliche Strukturen hinzufügen und Entwicklern die Wahl überlassen.

Nach allem, was gesagt wurde, möchte ich darauf hinweisen, dass Iota auch seine Nachteile hat.

  1. Es hat immer den Typ int,
  2. Sie müssen Werte im Kopf berechnen. Sie können viel Zeit verlieren, wenn Sie versuchen, Werte zu berechnen, und prüfen, ob es in Ordnung ist.
    Mit enums/const kann ich einfach F12 drücken und alle Werte sehen.
  3. Der Iota-Ausdruck ist ein Codeausdruck, den Sie auch zum Testen benötigen.
    In manchen Projekten habe ich aus diesen Gründen komplett auf die Verwendung von iota verzichtet.

Sie versuchen, ein Beispiel zu geben, bei dem die Verwendung von Aufzählungen lächerlich ist

Entschuldigen Sie meine Offenheit, aber nach diesem Kommentar denke ich nicht, dass Sie hier viel Boden haben, auf dem Sie stehen können.

Und ich habe nicht einmal das getan, was Sie sagen - das heißt, ein Beispiel dafür zu geben, wo die Verwendung von Aufzählungen lächerlich ist. Ich habe ein Beispiel genommen, das zeigen sollte, wie notwendig sie sind, und veranschaulichen sollte, wie sie weh tun.

Wir können vernünftigerweise anderer Meinung sein, aber wir sollten zumindest alle in gutem Glauben argumentieren.

Beispiel 1

Ich könnte Ihnen "Registernamen" als etwas geben, das wirklich nicht änderbar ist, aber in Bezug auf Protokollwerte bin ich fest davon überzeugt, dass die Position, dass sie willkürliche Werte für Erweiterbarkeit und Kompatibilität annehmen, vernünftig ist. Auch hier enthielt proto2 -> proto3 genau diese Änderung, und zwar aus gelernter Erfahrung.

Und so oder so, ich sehe nicht ein, warum ein Linter das nicht fangen könnte.

Ich habe alle 20 Konstanten und wenn sie den gleichen Typ und ähnliche Namen haben, kann ich Fehler machen. Wenn das Projekt groß ist, erhalten Sie diesen Fehler.

Wenn Sie sich bei Namen vertippen, helfen Ihnen geschlossene Aufzählungen nicht weiter. Nur wenn Sie die symbolischen Namen nicht verwenden und stattdessen int/string-literals verwenden.

Beispiel 2

Ich persönlich tendiere dazu, „Einzelpaket“ eher auf die Linie „kein großes Projekt“ zu setzen. Daher halte ich es für weitaus unwahrscheinlicher, dass Sie beim Erweitern einer Aufzählung einen Fall vergessen oder eine Codestelle ändern.

Und so oder so, ich sehe nicht ein, warum ein Linter das nicht fangen könnte.

Beispiel 3

Dies ist jedoch der häufigste Anwendungsfall für Enums. Typisches Beispiel: Dieses spezifische Problem verwendet sie als Rechtfertigung. Ein weiterer häufig genannter Fall sind Systemaufrufe – eine getarnte Client-Server-Architektur. Die Verallgemeinerung dieses Beispiels ist „jeder Code, in dem zwei oder mehr unabhängig entwickelte Komponenten solche Werte austauschen“, was unglaublich weit gefasst ist, die überwiegende Mehrheit der Anwendungsfälle für sie abdeckt und unter dem schrittweisen Reparaturmodell auch jede exportierte API .

FTR, ich versuche immer noch nicht, jemanden davon zu überzeugen, dass Aufzählungen schädlich sind (ich bin mir sicher, dass ich das nicht tun werde). Nur um zu erklären, wie ich zu dem Schluss gekommen bin, dass sie es sind und warum ich die Argumente zu ihren Gunsten nicht überzeugend finde.

Es hat immer den Typ int,

iota kann (nicht unbedingt, aber was auch immer), aber const -Blöcke nicht, sie können eine Vielzahl von konstanten Typen haben - tatsächlich eine Obermenge der am häufigsten vorgeschlagenen Enum-Implementierungen.

Sie müssen Werte im Kopf berechnen.

Auch dies können Sie nicht als Argument für Aufzählungen verwenden; Sie können die Konstanten genauso ausschreiben wie in einer Enum-Deklaration.

Der Iota-Ausdruck ist ein Codeausdruck, den Sie auch zum Testen benötigen.

Nicht jeder Ausdruck muss getestet werden. Wenn es sofort offensichtlich ist, ist das Testen übertrieben. Wenn nicht, schreiben Sie die Konstanten auf, das würden Sie sowieso in einem Test tun.

iota ist nicht die derzeit empfohlene Methode zum Erstellen von Aufzählungen in Go - const -Deklarationen sind es. iota dient nur als allgemeinere Methode, Tipparbeit zu sparen, wenn Sie fortlaufende oder formelhafte const-Deklarationen aufschreiben.

Und ja, die offenen Enums von Go haben offensichtlich Nachteile. Sie wurden oben ausführlich erwähnt: Sie können einen Fall in einem Switch vergessen, was zu Fehlern führt. Sie haben keinen Namensraum. Sie könnten versehentlich eine nicht symbolische Konstante verwenden, die am Ende ein ungültiger Wert ist (was zu Fehlern führt).
Aber es scheint mir produktiver zu sein, über diese Nachteile zu sprechen und sie mit den Nachteilen einer vorgeschlagenen Lösung zu vergleichen, als eine feste Lösung (Enum-Typ) zu nehmen und über ihre spezifischen Kompromisse zu streiten, um die Probleme zu lösen.

Für mich können die meisten Nachteile meist pragmatisch in der aktuellen Sprache gelöst werden, indem ein Linter-Tool const-Deklarationen einer bestimmten Art erkennt und ihre Verwendung überprüft. Namensräume können auf diese Weise nicht gelöst werden, was nicht so toll ist. Aber es kann auch eine andere Lösung für dieses Problem geben als Aufzählungen.

Ich könnte Ihnen "Registernamen" als etwas geben, das wirklich nicht änderbar ist, aber in Bezug auf Protokollwerte bin ich fest davon überzeugt, dass die Position, dass sie willkürliche Werte für Erweiterbarkeit und Kompatibilität annehmen, vernünftig ist. Auch hier enthielt proto2 -> proto3 genau diese Änderung, und zwar aus gelernter Erfahrung.

Deshalb sagte ich etablierte Werte. Die Basis im Fe-Wave-Format hat sich seit vielen Jahren nicht geändert und bietet eine hervorragende Back-Fähigkeit. Wenn es neue Werte gibt, können Sie Aufzählungen verwenden und einige Werte hinzufügen.

Wenn Sie sich bei Namen vertippen, helfen Ihnen geschlossene Aufzählungen nicht weiter. Nur wenn Sie die symbolischen Namen nicht verwenden und stattdessen int/string-literals verwenden.

Ja, es hilft mir nicht, gute Namen zu machen, aber sie können helfen, einige Werte mit einem Namen zu organisieren. In einigen Fällen beschleunigt es den Entwicklungsprozess. Es kann die Anzahl der Varianten mit Autotypisierung auf eine reduzieren.

Dies ist jedoch der häufigste Anwendungsfall für Enums. Typisches Beispiel: Dieses spezifische Problem verwendet sie als Rechtfertigung. Ein weiterer häufig genannter Fall sind Systemaufrufe – eine getarnte Client-Server-Architektur. Die Verallgemeinerung dieses Beispiels ist „jeder Code, in dem zwei oder mehr unabhängig entwickelte Komponenten solche Werte austauschen“, was unglaublich weit gefasst ist, die überwiegende Mehrheit der Anwendungsfälle für sie abdeckt und unter dem schrittweisen Reparaturmodell auch jede exportierte API .

Aber die Verwendung/Nichtverwendung von Konstanten/Aufzählungen beseitigt nicht den Kern des Problems, Sie müssen immer noch über die Rückkopierbarkeit nachdenken. Ich möchte sagen, dass das Problem nicht in Aufzählungen/Konstanten liegt, sondern in unseren Anwendungsfällen.

Ich persönlich tendiere dazu, „Einzelpaket“ eher auf die Linie „kein großes Projekt“ zu setzen. Daher halte ich es für weitaus unwahrscheinlicher, dass Sie beim Erweitern einer Aufzählung einen Fall vergessen oder eine Codestelle ändern.

In diesem Fall haben Sie immer noch die Vorteile der Namenskonvention und der Überprüfung der Kompilierzeit.

Nicht jeder Ausdruck muss getestet werden. Wenn es sofort offensichtlich ist, ist das Testen übertrieben. Wenn nicht, schreiben Sie die Konstanten auf, das würden Sie sowieso in einem Test tun.

Natürlich verstehe ich, dass nicht alle Codezeilen getestet werden müssen, aber wenn Sie einen Präzedenzfall haben, müssen Sie dies testen oder neu schreiben. Ich weiß, wie man das ohne Jota macht, aber mein altes Beispiel ist nur ein Witz.

Auch dies können Sie nicht als Argument für Aufzählungen verwenden; Sie können die Konstanten genauso ausschreiben wie in einer Enum-Deklaration.

Es ist kein Argument für Aufzählungen.

@Merovius

Wenn wir über geschlossene Aufzählungen sprechen (was die Leute normalerweise sagen, wenn sie über Aufzählungen sprechen) oder Aufzählungen mit Vollständigkeitsprüfung, dann sind sie sicherlich etwas Besonderes. Denn sie sind nicht erweiterbar.

Sie sind auch nicht sicher erweiterbar.

Wenn Sie haben

package p
type Enum int
const (
  A Enum = iota
  B
  C
)
func Make() Enum {...}
func Take(Enum) {...}

und

package q
import "p"
const D enum = p.C + 1

Innerhalb q ist es sicher, D zu verwenden (es sei denn, die nächste Version von p fügt ein eigenes Label für Enum(3) hinzu), aber nur so lange wie nie zuvor übergeben Sie es zurück an p : Sie können das Ergebnis von p.Make nehmen und seinen Zustand in D , aber wenn Sie p.Take aufrufen, müssen Sie sicherstellen, dass dies der Fall ist nicht übergeben wird q.D UND es muss sicherstellen, dass es nur eines von A , B , C erhält, oder Sie haben einen Fehler. Sie können dies umgehen, indem Sie dies tun

package q
import "p"
type Enum int
const (
    A = p.A
    B = p.B
    C = p.C
    D = C + 1
)
// needs to return an error if passed D or an unknown state of Enum
func To(Enum) (p.Enum, error) {...}
// needs to return an error if p.Enum has a value not known to the author
// at the time this package was written.
func From(p.Enum) (Enum, error) {...}

Mit oder ohne geschlossenen Typ in der Sprache haben Sie alle Probleme mit einem geschlossenen Typ, aber ohne dass der Compiler auf Sie aufpasst.

Ihr Beispiel deckt nur die Prozessgrenze ab (indem Sie über Netzwerkfehler sprechen), nicht die Paketgrenze. Wie verhalten sich Pakete, wenn ich FromTheNetwork einen "InvalidInternalState" hinzufüge (um etwas zu erfinden)? Muss ich ihre Schalter reparieren, bevor sie erneut kompilieren? Dann ist es im schrittweisen Reparaturmodell nicht erweiterbar. Benötigen sie einen Standardfall, um überhaupt zu kompilieren? Dann scheinen Aufzählungen keinen Sinn zu machen.

Mit nur Enum-Typen müssten Sie immer noch wie oben vorgehen und Ihre eigene Version mit den zusätzlichen Status- und Schreibkonvertierungsfunktionen definieren.

Summentypen sind jedoch auch dann zusammensetzbar, wenn sie als Aufzählungen verwendet werden, sodass Sie auf diese Weise einen ganz natürlich und sicher "erweitern" können. Ich habe ein Beispiel gegeben, aber um deutlicher zu sein, gegeben

package p
type Enum pick {
  A, B, C struct{}
}

Enum kann mit "erweitert" werden

package q
import "p"
type Enum pick {
  P p.Enum
  D struct{}
}

und dieses Mal ist es für eine neue Version von p absolut sicher, D hinzuzufügen. Der einzige Nachteil ist, dass Sie doppelt umschalten müssen, um von innerhalb eines q.Enum in den Zustand von p.Enum zu gelangen, aber es ist explizit und klar und, wie ich bereits erwähnt habe, Ihr Redakteur könnte das Skelett ausspucken der Schalter automatisch aus.

Aber dafür brauchen wir keine tatsächlichen Enums als Typen. Ein solches Linting-Tool könnte auch mithilfe von iota heuristisch nach const-Deklarationen suchen, wobei jeder Fall von einem bestimmten Typ ist, und dies als „eine Aufzählung“ betrachten und die gewünschten Überprüfungen durchführen. Ich wäre voll und ganz an Bord mit einem Tool, das diese "Aufzählungen nach Konvention" verwendet, um die automatische Vervollständigung oder das Linting zu unterstützen, dass jeder Schalter einen Standardwert haben muss oder dass sogar jeder (bekannte) Fall überprüft werden muss.

Dabei gibt es zwei Probleme.

Wenn Sie einen definierten ganzzahligen Typ mit Labels haben, die einer Teilmenge seiner Domäne über const/iota gegeben wurden:

Erstens kann es eine geschlossene oder offene Aufzählung darstellen. Während es hauptsächlich verwendet wird, um einen geschlossenen Typ zu simulieren, könnte es auch einfach verwendet werden, um häufig verwendeten Werten Namen zu geben. Betrachten Sie eine offene Aufzählung für ein imaginäres Dateiformat:

const (
  //Name is the ID of a record field
  Name Record = iota
  //EmpID is the ID of an employee ID field
  EmpID

  //Intermediate values are reserved for future versions

  //Custom is the base of custom fields. Any custom field must have a unique ID greater than Custom.
  Custom Record = 42
)

Dies bedeutet nicht, dass 0, 1 und 42 die Domäne des Datensatztyps sind. Der Vertrag ist viel subtiler und würde abhängige Typen zum Modellieren erfordern. (Das würde definitiv zu weit gehen!)

Zweitens könnten wir heuristisch annehmen, dass ein definierter ganzzahliger Typ mit konstanten Labels bedeutet, dass die Domäne eingeschränkt ist. Es würde ein falsches Positiv von oben erhalten, aber nichts ist perfekt. Wir könnten go/types verwenden, um diesen Pseudotyp aus den Definitionen zu extrahieren und dann alle Switches über Werte dieses Typs zu finden und sicherzustellen, dass sie alle die notwendigen Labels enthalten. Dies kann hilfreich sein, aber wir haben an dieser Stelle keine Vollständigkeit gezeigt. Wir haben die Abdeckung aller gültigen Werte sichergestellt, aber nicht bewiesen, dass keine ungültigen Werte erstellt wurden. Dies ist nicht möglich. Selbst wenn wir jede Quelle, Senke und Transformation von Werten finden und abstrakt interpretieren könnten, um statisch zu garantieren, dass kein ungültiger Wert erstellt wurde, könnten wir zur Laufzeit immer noch nichts über den Wert sagen, da Reflect das Wahre nicht kennt Domäne des Typs, da sie nicht im Typsystem codiert ist.

Es gibt hier eine Alternative zu Aufzählungs- und Summentypen, die dies umgeht, obwohl sie ihre eigenen Probleme hat.

Nehmen wir an, das Typliteral range m n erzeugt einen ganzzahligen Typ, der mindestens m und höchstens n ist (für alle v, m ≤ v ≤ n). Damit könnten wir die Domäne der Aufzählung einschränken, z

package p
type Enum range 0 2
const (
  A Enum = iota
  B
  C
)

Da die Größe der Domain = die Anzahl der Labels ist, kann man mit 100%iger Sicherheit sagen, ob eine switch-Anweisung alle Möglichkeiten ausschöpft. Um diese Aufzählung extern zu erweitern, müssten Sie unbedingt Typkonvertierungsfunktionen erstellen, um die Zuordnung zu handhaben, aber ich behaupte immer noch, dass Sie dies trotzdem tun müssen.

Natürlich ist das eigentlich eine überraschend subtile Typfamilie, die implementiert werden muss, und würde nicht so gut mit dem Rest von Go harmonieren. Es hat auch nicht viele Anwendungen außerhalb dieser und einiger Nischenanwendungsfälle.

Wir können uns nicht darüber einig sein, wie wichtig es ist, eine schrittweise Reparatur und Erweiterbarkeit möglich zu halten – zum Beispiel glauben viele Leute, dass die semantische Versionierung eine bessere Lösung für die Probleme ist, die sie löst. Aber wenn Sie sie für wichtig halten, ist es vollkommen berechtigt und vernünftig, Aufzählungen entweder als schädlich oder als sinnlos anzusehen. Und das war die Frage, auf die ich geantwortet habe: Wie können Leute vernünftigerweise den Kompromiss eingehen, überall eine Prüfung zu verlangen, anstatt sie im Compiler zu haben. Antwort: Indem man die Erweiterbarkeit und Weiterentwicklung von APIs wertschätzt, was diese Überprüfungen ohnehin am Einsatzort notwendig macht.

Für grundlegende Enum-Typen stimme ich zu. Zu Beginn dieser Diskussion wäre ich einfach unglücklich gewesen, wenn sie über Summentypen gewählt worden wären, aber jetzt verstehe ich, warum sie schädlich wären. Danke an dich und @griesemer , dass du mir das erklärt hast.

Für Summentypen denke ich, dass das, was Sie gesagt haben, ein triftiger Grund ist, nicht zu verlangen, dass Schalter zur Kompilierzeit vollständig sind. Ich denke immer noch, dass geschlossene Typen eine Reihe von Vorteilen haben und dass von den drei hier untersuchten Summentypen die flexibelsten sind, ohne die Nachteile der anderen. Sie ermöglichen das Schließen eines Typs, ohne die Erweiterbarkeit oder schrittweise Reparatur zu behindern, und vermeiden gleichzeitig Fehler, die durch illegale Werte verursacht werden, wie es bei jedem guten Typ der Fall ist.

Der Hauptgrund, warum ich Golang anstelle von Python und Javascript und anderen gängigen typlosen Sprachen verwende, ist die Typsicherheit. Ich habe viel mit Java gemacht und eine Sache, die ich in Golang vermisse, die Java bietet, sind sichere Aufzählungen.

Ich würde nicht zustimmen, die Typen mit Aufzählungen unterscheiden zu können. Wenn Sie Ints benötigen, bleiben Sie stattdessen einfach bei Ints, wie es Java tut. Wenn Sie sichere Aufzählungen benötigen, würde ich die folgende Syntax vorschlagen.

type enums enum { foo, bar, baz }

@rudolfschmidt , da stimme ich dir zu, so könnte es auch aussehen:

type DaysOfTheWeek enum {
  Monday
  Tuesday
}

Aber es hat einen kleinen Fallstrick – wir müssen in der Lage sein, die Kontrolle über enum zu übernehmen, wenn wir Daten validieren, sie in JSON umwandeln oder mit oder FS interagieren müssen.
Wenn wir blind davon ausgehen, dass enum eine Menge vorzeichenloser Ganzzahlen ist, können wir mit einem Jota enden.
Wenn wir innovativ sein wollen, müssen wir an die Benutzerfreundlichkeit denken.
Wie einfach kann ich beispielsweise überprüfen, ob der Wert in eingehendem JSON ein gültiges Element von enum ist?
Was ist, wenn ich Ihnen sage, dass sich die Software ändert?

Nehmen wir an, wir haben eine Liste von Kryptowährungen:

type CryptoCurrency enum {
  BTC
  ETH
  XMR
}

Wir tauschen Daten mit mehreren Drittsystemen aus. Sagen wir, Sie haben Tausende von ihnen.
Sie haben eine lange Historie, viele Daten im Speicher. Die Zeit vergeht, sagen wir, BitCoin stirbt schließlich. Niemand benutzt es.
Sie entscheiden sich also, es aus der Struktur zu löschen:

type CryptoCurrency enum {
  ETH
  XMR
}

Dies führt zu einer Änderung der Daten. Weil sich alle Werte von enum verschoben haben. Es ist in Ordnung für Sie. Sie können die Migration für Ihre Daten ausführen. Was ist mit Ihren Partnern, einige von ihnen bewegen sich nicht so schnell, einige haben keine Ressourcen oder können das aus verschiedenen Gründen nicht tun.
Aber Sie haben Daten von ihnen aufgenommen. Am Ende haben Sie also 2 Aufzählungen: alt und neu; und ein Data Mapper, der beides verwendet.
Das sagt uns, dass wir Enumerationen flexibel definieren und diese Art von Daten validieren und ordnen/entpacken müssen.

type CryptoCurrency enum {
  ETH = 1, // reminds const?
  XMR = 2
}
// this is real life case 
v := 3
if v is CryptoCurrency {
 // right?
} else {
 // nope, provide default value
 v = CryptoCurrency.ETH
}

Wir müssen über die Anwendbarkeit von Aufzählungen und Anwendungsfällen nachdenken.

Wir können 2 neue Schlüsselwörter lernen, wenn uns das Tausende von Zeilen Boilerplate-Code ersparen kann.

Der Mittelweg besteht in der Tat darin, die Möglichkeit zu haben, Enum-Werte zu validieren, ohne sie darauf einzuschränken, was diese Werte sein können. Der Enum-Typ bleibt ziemlich gleich - es ist ein Haufen benannter Konstanten. Eine Variable vom Typ enum kann einem beliebigen Wert des zugrunde liegenden Typs von enum entsprechen. Was Sie darüber hinaus hinzufügen, ist die Möglichkeit, einen Wert zu validieren, um zu sehen, ob er einen gültigen Enum-Wert enthält oder nicht. Und es könnte andere Boni wie Stringification geben.

Sehr oft bin ich in einer Situation, in der ich ein Protokoll (Protobuf oder Sparsamkeit) mit einer Reihe von Aufzählungen überall habe. Ich muss jeden von ihnen validieren und, wenn mir der Enum-Wert unbekannt ist, diese Nachricht wegwerfen und einen Fehler melden. Anders kann ich mit dieser Art von Nachricht nicht umgehen. Bei Sprachen, in denen enum nur ein Haufen Konstanten ist, bleibt mir nichts anderes übrig, als riesige Mengen von switch-Anweisungen zu schreiben, die alle möglichen Kombinationen prüfen. Das ist eine große Menge an Code, und Fehler sind zwangsläufig darin. Mit etwas wie C# kann ich die integrierte Unterstützung für die Validierung von Aufzählungen verwenden, was viel Zeit spart. Einige protobuf-Implementierungen tun dies tatsächlich intern und lösen eine Ausnahme aus, wenn dies der Fall ist. Ganz zu schweigen davon, wie einfach die Protokollierung wird – Sie erhalten Stringifizierung sofort einsatzbereit. Es ist schön, dass protobuf die Stringer-Implementierung für Sie generiert, aber nicht alles in Ihrem Code ist protobuf.

Aber die Möglichkeit, einen beliebigen Wert zu speichern, ist in anderen Fällen hilfreich, in denen Sie Nachrichten nicht wegwerfen, sondern etwas mit ihnen machen möchten, selbst wenn sie ungültig sind. Der Client kann normalerweise Nachrichten wegwerfen, aber auf der Serverseite müssen Sie oft alles in der Datenbank speichern. Inhalte wegzuwerfen ist dort keine Option.

Für mich ist es also ein echter Wert, Enum-Werte validieren zu können. Es würde mir Tausende von Zeilen von Boilerplate-Code ersparen, der nichts anderes als die Validierung tut.

Es scheint mir ziemlich einfach, diese Funktionalität als Werkzeug bereitzustellen. Ein Teil davon existiert bereits im Stringer-Tool. Wenn es ein Tool gibt, das Sie wie enumer Foo aufrufen würden, das fmt.Stringer Methoden für Foo generieren würde und (sagen wir) eine Known() bool Methode, die prüft, ob die Der gespeicherte Wert liegt im Bereich bekannter Werte. Würde das Ihre Probleme lindern?

@Merovius in Abwesenheit von irgendetwas anderem wäre es nützlich. Aber ich bin grundsätzlich gegen Autogen. Der einzige Fall, in dem es nützlich ist und einfach funktioniert, sind Dinge wie protobuf, bei denen Sie ein ziemlich stabiles Protokoll haben, das einmal kompiliert werden kann. Die Verwendung für Aufzählungen fühlt sich im Allgemeinen wie eine Krücke für ein vereinfachtes Typsystem an. Und wenn man sich ansieht, dass es bei Go nur um Typensicherheit geht, widerspricht das der Philosophie der Sprache selbst. Anstatt die Sprache zu unterstützen, fängst du an, diese Infrastruktur obendrauf zu entwickeln, die nicht wirklich Teil des Sprachökosystems ist. Lassen Sie externe Tools zum Überprüfen, nicht zum Implementieren dessen, was in der Sprache fehlt.

. Die Verwendung für Aufzählungen fühlt sich im Allgemeinen wie eine Krücke für ein vereinfachtes Typsystem an.

Weil es so ist – Gos Typensystem ist bekanntermaßen und absichtlich simpel. Aber das war nicht die Frage, die Frage war, ob es Ihre Probleme lindern würde. Abgesehen von "mag ich nicht" sehe ich nicht wirklich, wie es nicht funktioniert (wenn Sie sowieso von offenen Enumerationen ausgehen).

Und wenn man sich ansieht, dass es bei Go nur um Typensicherheit geht, widerspricht das der Philosophie der Sprache selbst.

Bei Go geht es nicht nur um Typensicherheit. Bei Sprachen wie Idris dreht sich alles um Typsicherheit. Bei Go geht es um groß angelegte technische Probleme, und als solches wird sein Design von den Problemen bestimmt, die es zu lösen versucht. Zum Beispiel erlaubt sein Typsystem, eine Vielzahl von Fehlern aufgrund von API-Änderungen abzufangen, und ermöglicht einige groß angelegte Refactorings. Aber es ist auch absichtlich einfach gehalten, um das Lernen zu erleichtern, die Divergenz der Codebasen zu reduzieren und die Lesbarkeit des Codes von Drittanbietern zu erhöhen.

Wenn also der Anwendungsfall, an dem Sie interessiert sind (offene Aufzählungen), ohne Sprachwechsel gelöst werden kann, durch ein Tool, das ebenso leicht lesbaren Code generiert, dann scheint das sehr der Philosophie von Go zu entsprechen. Insbesondere das Hinzufügen einer neuen Sprachfunktion, die eine Teilmenge der Funktionalität einer vorhandenen ist, scheint nicht im Einklang mit Gos Design zu stehen.

Also, um es noch einmal zu wiederholen: Es wäre hilfreich, wenn Sie erweitern könnten, wie die Verwendung eines Tools, das die Boilerplate generiert, mit der Sie sich befassen, das eigentliche Problem nicht löst - nicht zuletzt, weil das Verständnis dafür sowieso notwendig ist, um das Design des Features zu informieren .

Ich habe einige der Ideen aus der Diskussion kombiniert, was denkst du darüber?

Einige grundlegende Informationen:

  1. Sie können eine Aufzählung wie jeden anderen Typ erweitern.
  2. Sie werden wie eine Konstante gespeichert, jedoch mit dem Typnamen als Präfix. Grund: Wenn Sie aktuelle iota-enums verwenden, werden Sie wahrscheinlich den Namen des enums als Präfix jeder Konstante schreiben. Mit dieser Funktion können Sie es einfach vermeiden.
  3. Sie sind unveränderlich und werden wie jede andere Konstante behandelt.
  4. Sie können über Aufzählungen iterieren. Wenn Sie dies tun, verhalten sie sich wie eine Karte. Der Schlüssel ist der Aufzählungsname, der Wert ist der Aufzählungswert.
  5. Sie können einer Aufzählung Methoden hinzufügen, wie Sie es für jeden anderen Typ tun.
  6. Jeder Aufzählungswert muss automatisch generierte Methoden haben:
  7. Name() gibt den Namen der Aufzählungsvariable zurück
  8. Index() gibt den Aufzählungsindex zurück, der automatisch erhöht wird. Es beginnt dort, wo ein Array beginnt.

Code:
„Geh
Paket Haupt

//Beispiel A
type Country enum[struct] { //Enumerationen können andere Typen erweitern (siehe Beispiel B)
Austria("AT", "Austria", false) //Wird wie eine Konstante zugänglich sein, aber mit Typ als
Deutschland("DE", "Deutschland", true) //Präfix (zB Land.Österreich)

//The struct will automatically begin when it doesn't match the format EnumName(...) anymore
Code, CountryName string
HasMerkel bool //Totally awesome

}

//Enums können Methoden wie jeder andere Typ haben
func (c Land) test() {}

func main() {
println(Land.Österreich.Landesname) //Österreich
println(Land.Deutschland.Code) //DE

/* Prints:
Austria
0
Germany
1
 */
for name, country := range Country {
    println(name) //Austria
    println(name == country.Name()) //true ; also autogenerated 
    println(country.Index()) //Auto generated increasing index
}

}

//Beispiel B
Typ Coolness enum[int] {
Sehr Cool(10)
Cool(5)
Nicht Cool(0)
}```

@sinnlosername Ich denke, Aufzählungen sollten etwas sein, das sehr einfach zu verstehen ist. Die Kombination einiger der in der vorherigen Diskussion vorgestellten Ideen führt möglicherweise nicht unbedingt zur besten Idee für eine Aufzählung.

Ich glaube, dass folgendes einfach zu verstehen wäre:

Erklärung

type Day enum {
    Monday
    Tuesday
    ...
    Sunday
}

String-Konvertierung (unter Verwendung der Stringer -Schnittstelle):

func (d Day) String() string {
    switch d {
    case Monday:
        return "mon"
    case Tuesday:
        return "tues"
    ...
    case Sunday:
        return "sun"
    }
}

So einfach ist das. Der Vorteil davon ist, dass eine stärkere Typsicherheit beim Weitergeben von Aufzählungen ermöglicht wird.

Beispielnutzung

func IsWeekday(d Day) bool {
    return d != Saturday && d != Sunday
}

Wenn ich hier string Konstanten verwenden würde, um Day darzustellen, würde IsWeekday sagen, dass jede Zeichenfolge, die nicht "sat" oder "sun" ist ist ein Wochentag (dh was würde/sollte IsWeekday("abc") zurückgeben?). Im Gegensatz dazu ist der Bereich der oben gezeigten Funktion eingeschränkt, wodurch die Funktion in Bezug auf ihre Eingabe sinnvoller wird.

@ljeabmreosn

das sollte es wohl sein

func IsWeekday(d Day) bool {
    return d != Day.Saturday && d != Day.Sunday
}

Ich habe es aufgegeben, darauf zu warten, dass das Golang-Team die Sprache auf die notwendige Weise verbessert. Ich kann jedem empfehlen, einen Blick auf Rust Lang zu werfen, es hat bereits alle gewünschten Funktionen wie Enum und Generika und mehr.

Wir sind am 14. Mai 2018 und wir diskutieren immer noch über die Unterstützung von Enums. Ich meine, was zum Teufel? Ich persönlich bin von Golang enttäuscht.

Ich kann verstehen, dass es frustrierend werden kann, während man auf ein Feature wartet. Aber das Posten von nicht-konstruktiven Kommentaren wie diesem hilft nicht. Bitte halten Sie Ihre Kommentare respektvoll. Siehe https://golang.org/conduct.

Danke.

@agnivade Da muss ich @rudolfschmidt zustimmen. GoLang ist definitiv auch nicht meine Lieblingssprache aufgrund dieses Mangels an Funktionen, APIs und dieses zu großen Widerstands gegen Änderungen oder das Akzeptieren der Fehler der Go-Ersteller in der Vergangenheit. Aber ich habe im Moment keine Wahl, weil ich nicht der Entscheidungsträger war, welche Sprache ich für mein letztes Projekt an meinem Arbeitsplatz wählen sollte. Also muss ich mit all seinen Mängeln arbeiten. Aber um ehrlich zu sein, ist es wie eine Folter, Codes in GoLang zu schreiben;-)

Ich habe es aufgegeben, darauf zu warten, dass das Golang-Team die Sprache auf die notwendige Weise verbessert.

  • Das Wort notwendig bedeutet nicht „was ich will“.

Eigentlich sind Kernmerkmale jeder modernen Sprache notwendig. GoLang hat einige gute Eigenschaften, aber es wird nicht überleben, wenn das Projekt konservativ bleibt. Features wie Enums oder Generics haben keine Nachteile für Leute, die sie nicht mögen, aber sie haben viele Vorteile für Leute, die sie verwenden möchten.

Und sag mir nicht "aber gehen will einfach bleiben". Es gibt einen großen Unterschied zwischen "einfach" und "keine wirklichen Funktionen". Java ist sehr einfach, aber es fehlen viele Funktionen. Also sind die Java-Entwickler entweder Zauberer oder dieses Argument ist einfach schlecht.

Eigentlich sind Kernmerkmale jeder modernen Sprache notwendig.

Sicher. Diese Kernmerkmale werden als Turing-Vollständigkeit bezeichnet. _Alles_ andere ist eine Designentscheidung. Es gibt viel Platz zwischen Turing-Vollständigkeit und C++ (zum Beispiel) und in diesem Platz können Sie viele Sprachen finden. Die Verteilung legt nahe, dass kein globales Optimum existiert.

GoLang hat einige gute Eigenschaften, aber es wird nicht überleben, wenn das Projekt konservativ bleibt.

Möglicherweise. Bisher wächst es noch. Meiner Meinung nach würde es nicht noch wachsen, wenn es nicht konservativ gewesen wäre. Unsere beiden Meinungen sind subjektiv und technisch nicht viel wert. Es sind die Erfahrung und der Geschmack der Designer, die maßgebend sind. Es ist in Ordnung, eine andere Meinung zu haben, aber das kann nicht garantieren, dass die Designer sie teilen.

Übrigens, wenn ich mir vorstelle, was Go heute wäre, wenn 10 % der von den Leuten geforderten Funktionen übernommen würden, würde ich Go jetzt wahrscheinlich nicht mehr verwenden.

Eigentlich hast du gerade das wichtigste Argument meiner Antwort verpasst. Vielleicht, weil es bereits ein Gegenpol zu einigen der Dinge ist, die du gesagt hast.

"Features wie Enums oder Generics haben keine Nachteile für Leute, die sie nicht mögen, aber sie haben viele Vorteile für Leute, die sie nutzen wollen."

Und warum ist diese Konservativität Ihrer Meinung nach ein Grund für das Wachstum von Golang? Ich denke, das hängt eher mit der Effizienz von Golang und den großartigen Standardbibliotheken zusammen.

Auch Java erlebte eine Art "Crash", als sie versuchten, wichtige Dinge in Java 9 zu ändern, was wahrscheinlich dazu führte, dass viele Leute nach einer Alternative suchten. Aber schauen Sie sich Java vor diesem Absturz an. Es wuchs ständig, weil es immer mehr Funktionen bekam, die das Leben der Entwickler einfacher machten.

"Features wie Enums oder Generics haben keine Nachteile für Leute, die sie nicht mögen, aber sie haben viele Vorteile für Leute, die sie nutzen wollen."

Das stimmt ganz klar nicht. Jedes Feature schafft es schließlich in die stdlib und/oder Pakete, die ich importieren möchte. _Jeder_ wird sich mit den neuen Funktionen auseinandersetzen müssen, egal ob er sie mag oder nicht.

Bisher wächst es noch. Meiner Meinung nach würde es nicht noch wachsen, wenn es nicht konservativ gewesen wäre

Ich glaube nicht, dass sein langsames Wachstum (wenn überhaupt) auf Konservativität zurückzuführen ist, sondern eher auf Standardbibliotheken, bereits vorhandene Sprachfunktionen und Werkzeuge. Das hat mich hierher gebracht. Das Hinzufügen von Sprachfeatures würde für mich daran nichts ändern.

Wenn wir uns C# und Typescript oder sogar Rust/Swift ansehen. Sie fügen neue Funktionen wie verrückt hinzu. C# schwankt immer noch in den Top-Sprachen auf und ab. Typoskript wächst sehr schnell. Gleiches gilt für Rust/Swift. Go hingegen explodierte in den Jahren 2009 und 2016 in der Popularität. Aber dazwischen wuchs es überhaupt nicht und verlor sogar. Go hat neuen Entwicklern nichts zu bieten, wenn sie bereits davon wussten und sich aus irgendeinem Grund nicht früher dafür entschieden haben. Eben weil Go in seinem Design stagniert. Andere Sprachen können hinzugefügt werden, nicht weil sie nichts anderes zu tun haben, sondern weil die tatsächliche Benutzerbasis es verlangt. Die Menschen brauchen neue Funktionen, damit ihre Codebasis in sich ständig ändernden Problembereichen relevant bleibt. Wie async/await. Es wurde benötigt, um ein tatsächliches Problem zu lösen. Kein Wunder, dass Sie es jetzt in vielen Sprachen sehen können.

Irgendwann wird es Go 2 geben und Sie können absolut sicher sein, dass es viele neue Entwickler bringen wird. Nicht weil es neu und glänzend ist, sondern weil neue Funktionen jemanden überzeugen könnten, endlich zu wechseln oder es auszuprobieren. Wenn Konservativität so wichtig wäre, hätten wir sogar diese Vorschläge.

Ich glaube nicht, dass sein langsames Wachstum (wenn überhaupt) auf Konservativität zurückzuführen ist, sondern eher auf Standardbibliotheken, bereits vorhandene Sprachfunktionen und Werkzeuge. Das hat mich hierher gebracht.

Und das ist das Ergebnis von konservativ sein. Wenn die Sprache etwa alle [halben] Jahre etwas/alles kaputt macht, würdest du nichts von dem, was du an Go schätzt, weil es viel weniger Leute geben wird, die dir das bringen.

Das Hinzufügen von Sprachfeatures würde für mich daran nichts ändern.

Bist du dir da sicher? Siehe oben.


Übrigens, haben Sie die Ergebnisse der Umfrage 2017 gesehen?

Wenn die Sprache etwa alle [halben] Jahre etwas/alles kaputt macht

Dann mach nichts kaputt. C# fügte eine Menge Funktionen hinzu und verletzte nie die Abwärtskompatibilität. Auch das ist für sie keine Option. Dasselbe gilt für C++, denke ich. Wenn Go keine Funktion hinzufügen kann, ohne etwas zu beschädigen, dann ist es ein Problem mit Go und möglicherweise mit der Art und Weise, wie es implementiert wird.

Übrigens, haben Sie die Ergebnisse der Umfrage 2017 gesehen?

Mein Kommentar basiert auf Umfragen von 2017/2018, dem TIOBE-Index und meinen allgemeinen Beobachtungen, was mit verschiedenen Sprachen los ist.

@cznik
Jeder muss mit ihnen umgehen, aber Sie müssen sie nicht verwenden. Wenn Sie es vorziehen, Ihren Code mit Iota-Enumerationen und -Maps zu schreiben, können Sie dies immer noch tun. Und wenn Sie Generika nicht mögen, dann verwenden Sie Bibliotheken ohne sie. Java beweist, dass beides möglich ist.

Dann mach nichts kaputt.

Gute Idee. Allerdings sind viele, wenn nicht die meisten der vorgeschlagenen Änderungen an der Sprache, _einschließlich dieser genauen_, Breaking Changes.

C# fügte eine Menge Funktionen hinzu und verletzte nie die Abwärtskompatibilität.

Bitte überprüfen Sie Ihre Fakten: Visual C# 2010 Breaking Changes . (Erstes Websuchergebnis, ich kann nur raten, ob es das einzige Beispiel ist oder nicht.)

Mein Kommentar basiert auf Umfragen von 2017/2018, dem TIOBE-Index und meinen allgemeinen Beobachtungen, was mit verschiedenen Sprachen los ist.

Nun, wie können Sie dann sehen, dass die Sprache nicht wächst, während die Umfrageergebnisse ein jährliches Wachstum von 70 % bei der Anzahl der Befragten zeigen?

Wie definieren Sie „Breaking Changes“? Jede Zeile des Go-Codes würde auch nach dem Hinzufügen von Enums oder Generika funktionieren.

Jeder muss mit ihnen umgehen, aber Sie müssen sie nicht verwenden. Wenn Sie es vorziehen, Ihren Code mit Iota-Enumerationen und -Maps zu schreiben, können Sie dies immer noch tun. Und wenn Sie Generika nicht mögen, dann verwenden Sie Bibliotheken ohne sie. Java beweist, dass beides möglich ist.

Ich kann nicht zustimmen. Sobald die Sprache zum Beispiel Generika bekommt, werden sie überall verwendet, auch wenn ich sie nicht verwende. Auch wenn intern ohne Änderung der API. Das Ergebnis ist, dass ich sehr von ihnen betroffen bin, da es sicher keine Möglichkeit gibt, der Sprache Generika hinzuzufügen, ohne das Erstellen von Programmen, die sie verwenden, zu verlangsamen. Auch bekannt als „Kein kostenloses Mittagessen“.

Wie definieren Sie „Breaking Changes“? Jede Zeile des Go-Codes würde auch nach dem Hinzufügen von Enums oder Generika funktionieren.

Natürlich nicht. Dieser Code würde mit diesem Vorschlag nicht mehr kompiliert:

package foo

var enum = 42

Das Wort notwendig bedeutet nicht „was ich will“.

sicher, es bedeutet nicht, und ich habe es nie so gemeint. Natürlich können Sie antworten, dass solche Features nicht notwendig sind, aber dann kann ich beantworten, was im Allgemeinen notwendig ist. Nichts ist notwendig und wir können zu Stift und Papier zurückkehren.

Golang behauptet, eine Sprache für große Teams zu sein. Ich bin mir nicht sicher, ob Sie golang verwenden können, um große Codebasen zu entwickeln. Dazu benötigen Sie eine statische Kompilierung und Typprüfung, um Laufzeitfehler so weit wie möglich zu vermeiden. Wie können Sie dies ohne Aufzählungen und Generika tun? Diese Funktionen sind nicht einmal ausgefallen oder nett zu haben, sondern absolut notwendig für eine ernsthafte Entwicklung. Wenn Sie sie nicht haben, verwenden Sie am Ende überall Schnittstellen{}. Welchen Sinn haben Datentypen, wenn Sie gezwungen sind, Schnittstellen{} in Ihrem Code zu verwenden?

Sicher, wenn Sie keine Wahl haben, werden Sie es auch tun, aber warum sollten Sie, wenn Sie Alternativen wie Rust haben, die all das Zeug bereits bieten und noch schneller in der Ausführung sind, als Golang jemals sein kann? Ich frage mich wirklich, ob go mit dieser Denkweise eine Zukunft hat:

Das Wort notwendig bedeutet nicht „was ich will“.

Ich respektiere jeden Beitrag zu Open Source und wenn Golang ein Hobbyprojekt ist, ist es gut, wie es ist, aber Golang will ernst genommen werden und ist im Moment eher ein Spielzeug für einige gelangweilte Entwickler, und ich sehe keinen Willen, das zu ändern.

Die API muss nicht geändert werden, nur neue API-Teile könnten Generika verwenden, aber es gibt wahrscheinlich immer Alternativen ohne Generika im Internet.

Und sowohl ein etwas langsameres Kompilieren als auch Variablen namens "enum" sind minimale Effekte. Tatsächlich werden 99% der Menschen es nicht einmal bemerken und die anderen 1% müssen nur einige kleine Änderungen hinzufügen, die tolerierbar sind. Das ist nicht vergleichbar mit zB Javas Puzzle, das alles durcheinander bringt.

Und sowohl ein etwas langsameres Kompilieren als auch Variablen namens "enum" sind minimale Effekte. Tatsächlich werden 99% der Menschen es nicht einmal bemerken und die anderen 1% müssen nur einige kleine Änderungen hinzufügen, die tolerierbar sind.

Jeder würde sich freuen, wenn jemand mit einem Design und einer Umsetzung kommen könnte, die eine so wunderbare Leistung erbringen. Bitte tragen Sie zu #15292 bei.

Wenn dies jedoch ein Spiel mit dem Namen "Ziehen von Zahlen zu meinen Gunsten ohne irgendwelche Hintergrunddaten" ist, dann tut es mir leid, aber ich nehme nicht teil.

Haben Sie irgendwelche Zahlen zum Geschwindigkeitsunterschied mit Generika?

Und ja, diese Zahlen sind nicht durch irgendwelche Daten gestützt, weil sie nur sagen sollen, dass die Wahrscheinlichkeit, Variablen namens "enum" zu haben, nicht sehr hoch ist.

Ich möchte alle daran erinnern, dass viele Leute diese Ausgabe abonniert haben, um speziell zu fragen, ob und wie Enums zu Go hinzugefügt werden könnten. Die allgemeinen Fragen "Ist Go eine gute Sprache?" und "Sollte Go sich mehr auf die Bereitstellung von Funktionen konzentrieren?" werden wahrscheinlich besser in einem anderen Forum diskutiert.

Haben Sie irgendwelche Zahlen zum Geschwindigkeitsunterschied mit Generika?

Nein, deswegen habe ich keine gepostet. Ich habe nur geschrieben, dass die Kosten nicht Null sein können.

Und ja, diese Zahlen sind nicht durch irgendwelche Daten gestützt, weil sie nur sagen sollen, dass die Wahrscheinlichkeit, Variablen namens "enum" zu haben, nicht sehr hoch ist.

Das ist durcheinander. Bei Slowdown ging es um Generika. Bei „enum“ ging es um Abwärtskompatibilität, und Ihr falscher „_Jede_ Zeile des Go-Codes würde auch nach dem Hinzufügen von Enums oder Generika funktionieren.“ beanspruchen. (betont meine)

@Merovius Du hast Recht, ich halte jetzt die Klappe.

Um dies auf Enum-Typen zurückzubringen, worum es in dieser Ausgabe geht, verstehe ich das Argument, warum Go Generika benötigt, vollständig, aber ich bin viel wackeliger, wenn es darum geht, warum Go Enum-Typen benötigt. Tatsächlich habe ich dies oben in https://github.com/golang/go/issues/19814#issuecomment -290878151 gefragt und bin immer noch unsicher. Wenn es darauf eine gute Antwort gab, habe ich sie verpasst. Könnte es jemand wiederholen oder darauf hinweisen? Danke.

@ianlancetaylor Ich denke nicht, dass der Anwendungsfall kompliziert ist, da ich einen typsicheren Weg suchen möchte, um sicherzustellen, dass ein Wert zu einem vordefinierten Satz von Werten gehört, was heute in Go nicht möglich ist. Die einzige Problemumgehung besteht darin, jeden möglichen Einstiegspunkt in Ihren Code manuell zu validieren, einschließlich RPCs und Funktionsaufrufen, was von Natur aus unzuverlässig ist. Die anderen syntaktischen Feinheiten beim Iterieren erleichtern viele gängige Anwendungsfälle. Ob Sie das wertvoll finden oder nicht, ist subjektiv, und offensichtlich war keines der oben genannten Argumente überzeugend für die Machthaber, also habe ich im Wesentlichen aufgegeben, dies jemals auf sprachlicher Ebene anzusprechen.

@ianlancetaylor : Alles hat mit Typensicherheit zu tun. Sie verwenden Typen, um das Risiko von Laufzeitfehlern aufgrund eines Tippfehlers oder der Verwendung inkompatibler Typen zu minimieren. Im Moment können Sie in go schreiben

if enumReference == 1

weil Aufzählungen im Moment nur Zahlen oder andere primitive Datentypen sind.

Dieser Code sollte überhaupt nicht möglich sein und sollte vermieden werden. Dieselbe Diskussion, die Sie vor Jahren in der Java-Community geführt haben, ist der Grund, warum sie Enums eingeführt haben, weil sie die Bedeutung verstanden haben.

Du solltest nur schreiben können

if enumReference == enumType

Sie brauchen nicht allzu viel Fantasie, um sich vorzustellen, in welchen Szenarien if enumReference == 1 versteckter passieren und zu zusätzlichen Problemen führen können, die Sie erst zur Laufzeit sehen werden.

Ich möchte nur erwähnen: Go hat sein Potenzial, aber es ist seltsam, dass Dinge und Konzepte, die seit Jahren bewährt und verstanden werden, hier so diskutiert werden, wie Sie neue Konzepte oder Paradigmen der Programmierung diskutieren. Wenn Sie eine alternative Möglichkeit haben, Typsicherheit zu gewährleisten, gibt es vielleicht etwas Besseres als Aufzählungen, aber ich sehe es nicht.

Go hat sein Potenzial, aber es ist seltsam, dass Dinge und Konzepte, die seit Jahren bewährt und verstanden werden, hier so diskutiert werden, wie Sie neue Konzepte oder Paradigmen der Programmierung diskutieren.

Afais, besonders wenn man die anderen Diskussionen über Generika, Summentypen usw. verfolgt ... es geht nicht so sehr darum, ob man es haben sollte, sondern wie man es implementiert. Das Typsystem von Java ist extrem erweiterbar und gut spezifiziert. Das ist ein riesiger Unterschied.

In Go versuchen die Leute, Möglichkeiten zu finden, der Sprache Funktionen hinzuzufügen, ohne die Komplexität des Compilers zu erhöhen. Das funktioniert normalerweise nicht so gut und lässt sie diese anfänglichen Ideen aufgeben.

Auch wenn ich diese Prioritäten in ihrer jetzigen Form und Qualität für ziemlich unsinnig halte, sollten Sie sich am besten eine möglichst einfache und am wenigsten störende Implementierung einfallen lassen . Alles andere bringt dich nicht weiter, imo.

@derekperkins @rudolfschmidt Danke. Ich möchte klarstellen, dass C++ zwar Aufzählungstypen hat, die von Ihnen vorgeschlagenen Funktionen jedoch nicht in C++ enthalten sind. Daran ist also nichts Offensichtliches.,

Wenn eine Variable eines Aufzählungstyps nur Werte dieser Aufzählung akzeptieren kann, wäre dies im Allgemeinen nutzlos. Insbesondere muss eine Konvertierung von einer beliebigen Ganzzahl in den Enum-Typ erfolgen; Andernfalls können Sie keine Aufzählungen über eine Netzwerkverbindung senden. Nun, Sie können, aber Sie müssen einen Schalter mit einem Fall für jeden Enum-Wert schreiben, was wirklich mühsam erscheint. Generiert der Compiler also bei einer Konvertierung eine Überprüfung, ob der Wert während der Typkonvertierung ein gültiger Enum-Wert ist? Und gerät es in Panik, wenn der Wert ungültig ist?

Müssen Enum-Werte sequentiell sein, oder können sie wie in C++ jeden beliebigen Wert annehmen?

In Go sind Konstanten nicht typisiert. Wenn wir also Konvertierungen von Ganzzahlen in einen Aufzählungstyp zulassen, wäre es seltsam, if enumVal == 1 zu verbieten. Aber ich denke, wir könnten.

Eines der allgemeinen Designprinzipien von Go ist, dass Leute, die Go schreiben, Code schreiben, nicht Typen. Ich sehe noch keinen Vorteil von Enum-Typen, der uns beim Schreiben von Code hilft. Sie scheinen eine Reihe von Typbeschränkungen hinzuzufügen, die wir im Allgemeinen in Go nicht haben. Zum Guten oder zum Schlechten bietet Go keine Mechanismen, um den Wert von Typen zu kontrollieren. Also muss ich sagen, dass mir das Argument für das Hinzufügen von Enums zu Go noch nicht überzeugend erscheint.

Ich werde mich wiederholen, aber ich bin dafür, die Aufzählungen so beizubehalten, wie sie heute sind, und ihnen Funktionen hinzuzufügen:

  • Der Enum-Typ hat einen zugrunde liegenden Werttyp und ein paar benannte Konstanten, die ihm zugeordnet sind
  • Der Compiler sollte die Konvertierung von einem beliebigen Wert in einen Enum-Wert zulassen, solange die zugrunde liegenden Typen kompatibel sind. Jeder Wert vom Typ int sollte in einen beliebigen Integer-Aufzählungstyp konvertierbar sein.
  • Konvertierung, die zu einem ungültigen Aufzählungswert führt, ist zulässig. Der Enum-Typ sollte keine Beschränkungen auferlegen, welche Werte eine Variable annehmen kann.

Was es darüber hinaus bietet, ist:

  • Stringifizierung von Enum-Werten. Aus meiner Erfahrung sehr nützlich für die Benutzeroberfläche und die Protokollierung. Wenn der Aufzählungswert gültig ist, gibt die Stringifizierung den Namen der Konstante zurück. Wenn es ungültig ist, gibt es eine Zeichenfolgendarstellung des zugrunde liegenden Werts zurück. Wenn -1 kein gültiger Aufzählungswert eines Aufzählungstyps Foo ist, sollte die Stringifizierung nur -1 zurückgeben.
  • Erlauben Sie dem Entwickler festzustellen, ob der Wert zur Laufzeit ein gültiger Enum-Wert ist. Sehr nützlich bei der Arbeit mit jeder Art von Protokoll. Wenn sich Protokolle weiterentwickeln, könnten neue Enum-Werte eingeführt werden, von denen das Programm nichts weiß. Oder es könnte ein einfacher Fehler sein. Im Moment müssen Sie entweder sicherstellen, dass Enum-Werte streng sequentiell sind (was Sie nicht immer erzwingen können) oder jeden möglichen Wert manuell überprüfen. Diese Art von Code wird sehr schnell sehr umfangreich und Fehler sind vorprogrammiert.
  • Ermöglichen Sie möglicherweise dem Entwickler, alle möglichen Werte eines Aufzählungstyps aufzuzählen. Ich habe gesehen, wie Leute hier danach gefragt haben, andere Sprachen haben das auch, aber ich kann mich nicht erinnern, dass ich das jemals selbst gebraucht hätte, also habe ich keine persönliche Erfahrung dafür.

Meine Rechtfertigung dreht sich alles um das Schreiben von Code und das Vermeiden von Fehlern. All diese Aufgaben sind mühsam und für Entwickler nicht von Hand zu erledigen oder sogar externe Tools einzuführen, die den Code verkomplizieren und Skripte erstellen. Diese Funktionen decken alles ab, was ich von Enumerationen benötige, ohne sie übermäßig zu verkomplizieren und einzuschränken. Ich glaube nicht, dass Go so etwas wie Enums in Swift oder sogar Java braucht.


Es gab Diskussionen darüber, zur Kompilierzeit zu validieren, dass die switch-Anweisung jeden möglichen Enum-Wert abdeckt. Mit meinem Vorschlag wird es nutzlos sein. Das Durchführen von Erschöpfungsprüfungen deckt ungültige Enum-Werte nicht ab, sodass Sie immer noch eine Standard-Groß-/Kleinschreibung haben müssen, um diese zu behandeln. Dies ist erforderlich, um eine schrittweise Codereparatur zu unterstützen. Ich denke, das einzige, was wir hier tun können, ist eine Warnung zu erzeugen, wenn die switch-Anweisung keinen Standardfall hat. Das geht aber auch ohne Sprachumstellung.

@ianlancetaylor Ich denke, deine Argumentation hat einige Mängel.

Wenn eine Variable eines Aufzählungstyps nur Werte dieser Aufzählung akzeptieren kann, wäre dies im Allgemeinen nutzlos. Insbesondere muss eine Konvertierung von einer beliebigen Ganzzahl in den Enum-Typ erfolgen; Andernfalls können Sie keine Aufzählungen über eine Netzwerkverbindung senden.

Abstraktion für den Programmierer ist in Ordnung; Go bietet viele Abstraktionen. Der folgende Code wird beispielsweise nicht kompiliert:

package main

import "fmt"

const NULL = 0x0

func main() {
    str := "hello"
    if &str == NULL {
        fmt.Println("str is null")
    }
}

aber in C würde ein Programm dieses Stils kompilieren. Dies liegt daran, dass Go stark typisiert ist und C nicht.

Die Indizes von Aufzählungen können intern gespeichert werden, sind aber für den Benutzer als Abstraktion verborgen, ähnlich wie die Adressen von Variablen.

@zerkms Ja, das ist eine Möglichkeit, aber angesichts des Typs von d sollte Typrückschluss möglich sein; Die qualifizierte Verwendung von Aufzählungen (wie in Ihrem Beispiel) ist jedoch etwas einfacher zu lesen.

@ianlancetaylor , das ist eine sehr C-Version von Aufzählungen, von denen Sie sprechen. Ich bin mir sicher, dass viele Leute das mögen würden, aber imo:

Enum-Werte sollten keine numerischen Eigenschaften haben. Die Werte jedes Enum-Typs sollten ihr eigenes endliches Universum diskreter Labels sein, die zur Kompilierzeit erzwungen werden und nichts mit Zahlen oder anderen Enum-Typen zu tun haben. Das Einzige, was Sie mit einem Paar solcher Werte tun können, ist == oder != . Andere Operationen können als Methoden oder mit Funktionen definiert werden.

Die Implementierung wird diese Werte zu ganzen Zahlen herunterkompilieren, aber das ist keine grundlegende Sache mit einem legitimen Grund, dem Programmierer direkt ausgesetzt zu werden, außer durch unsichere oder Reflexion. Aus dem gleichen Grund, aus dem Sie nicht bool(0) machen können, um false zu bekommen.

Wenn Sie eine Aufzählung in oder von einer Zahl oder einem anderen Typ konvertieren möchten, schreiben Sie alle Fälle aus und fügen eine der Situation angemessene Fehlerbehandlung hinzu. Wenn das mühsam ist, verwenden Sie einen Codegenerator wie Stringer oder zumindest etwas, um die Fälle in der switch-Anweisung auszufüllen.

Wenn Sie den Wert außerhalb des Prozesses senden, ist ein int gut, wenn Sie einem klar definierten Standard folgen oder wissen, dass Sie mit einer anderen Instanz Ihres Programms sprechen, die aus der Quelle kompiliert wurde, oder wenn Sie Dinge erstellen müssen passen in den kleinstmöglichen Raum, auch wenn dies Probleme verursachen könnte, aber im Allgemeinen gilt keines davon, und es ist besser, eine Zeichenfolgendarstellung zu verwenden, damit der Wert nicht von der Quellreihenfolge der Typdefinition beeinflusst wird. Sie möchten nicht, dass das Grün von Prozess A zu Blau von Prozess B wird, weil jemand anderes entschieden hat, dass Blau vor Grün hinzugefügt werden sollte, um die Definition alphabetisch zu halten: Sie wollen unrecognized color "Blue" .

Es ist eine gute und sichere Möglichkeit, eine Reihe von Zuständen abstrakt darzustellen. Es überlässt dem Programm, zu definieren, was diese Zustände bedeuten.

(Natürlich möchten Sie diesen Staaten oft Daten zuordnen, und die Art dieser Daten variiert von Staat zu Staat ...)

@ljeabmreosn Mein Punkt war, dass, wenn Go die Konvertierung von Ganzzahlen in Aufzählungstypen zulässt, es für eine nicht typisierte Konstante selbstverständlich wäre, automatisch in einen Aufzählungstyp konvertiert zu werden. Ihr Gegenbeispiel ist anders, da Go keine Konvertierung von Ganzzahlen in Zeigertypen zulässt.

@jimmyfrasche Wenn Sie einen Schalter schreiben müssen, um zwischen Ganzzahlen und Enum-Typen zu konvertieren, dann stimme ich zu, dass das in Go sauber funktionieren würde, aber ehrlich gesagt scheint es nicht nützlich genug zu sein, um die Sprache selbst hinzuzufügen. Es wird ein Spezialfall von Summentypen, für die siehe #19412.

Hier gibt es viele Vorschläge.

Ein allgemeiner Kommentar: Für alle Vorschläge, die keinen zugrunde liegenden Wert (z. B. einen Int) offenlegen, den Sie für eine Aufzählung konvertieren können, sind hier einige Fragen zu beantworten.

Was ist der Nullwert eines Aufzählungstyps?

Wie kommt man von einem Enum zum anderen? Ich vermute, dass Wochentage für viele Leute ein kanonisches Beispiel für eine Aufzählung sind, aber man sollte vernünftigerweise von Mittwoch auf Donnerstag „erhöhen“. Dafür möchte ich keine große Switch-Anweisung schreiben müssen.

(Auch in Bezug auf die „Stringifizierung“ ist die korrekte Zeichenfolge für einen Wochentag sprach- und gebietsschemaabhängig.)

@josharian-Stringifizierung bedeutet normalerweise, dass Namen von Enum-Werten vom Compiler automatisch in Zeichenfolgen umgewandelt werden. Keine Lokalisierung oder ähnliches. Wenn Sie etwas darauf aufbauen möchten, wie z. B. Lokalisierung, dann tun Sie dies auf andere Weise, und andere Sprachen bieten dafür umfangreiche Sprach- und Framework-Tools.

Beispielsweise haben einige C#-Typen eine ToString -Überschreibung, die auch Kulturinformationen akzeptiert. Oder Sie können das DateTime -Objekt selbst und seine ToString -Methode verwenden, die sowohl Format- als auch Kulturinformationen akzeptiert. Aber diese Überschreibungen sind nicht Standard, object Klasse, von der jeder erbt, hat nur ToString() . Ziemlich ähnlich wie die Stringer-Schnittstelle in Go.

Ich denke also, dass die Lokalisierung außerhalb dieses Vorschlags und der Aufzählungen im Allgemeinen liegen sollte. Wenn du es umsetzen willst, dann mach es anders. Wie zum Beispiel eine benutzerdefinierte Stringer-Schnittstelle.

@josharian Da es sich bei der Implementierung immer noch um ein int handelt und Nullwerte alle Bits Null sind, wäre der Nullwert der erste Wert in der Quellreihenfolge. Das ist irgendwie ein Leck der Intigkeit, aber eigentlich ganz nett, weil Sie den Nullwert wählen können, um beispielsweise zu entscheiden, ob eine Woche am Montag oder am Sonntag beginnt. Weniger schön ist natürlich, dass die Reihenfolge der verbleibenden Terme keinen solchen Einfluss hat und dass eine Neuordnung der Werte nicht triviale Auswirkungen haben kann, wenn Sie das erste Element ändern. Dies ist jedoch nicht wirklich anders als const/iota.

Restringifizierung, was @creker gesagt hat. Eine Erweiterung würde ich aber erwarten

var e enum {
  Sunday
  Monday
  //etc.
}
fmt.Println(reflect.ValueOf(e))

um Sonntag nicht 0 zu drucken. Das Label ist der Wert, nicht seine Darstellung.

Um es klar zu sagen, ich sage nicht, dass es eine implizite String-Methode haben sollte – nur dass die Labels als Teil des Typs gespeichert und durch Reflektion zugänglich sind. (Vielleicht ruft Println Label() auf einem reflect.Value aus einer Aufzählung oder so etwas auf? Ich habe nicht genau untersucht, wie fmt sein Voodoo macht.)

Wie kommt man von einem Enum zum anderen? Ich vermute, dass Wochentage für viele Leute ein kanonisches Beispiel für eine Aufzählung sind, aber man sollte vernünftigerweise von Mittwoch auf Donnerstag „erhöhen“. Dafür möchte ich keine große Switch-Anweisung schreiben müssen.

Ich denke, Reflexion oder ein großer Schalter ist das Richtige. Gängige Muster können leicht mit go generate ausgefüllt werden, um Methoden für den Typ oder Factory-Funktionen dieses Typs zu erstellen (und vielleicht sogar vom Compiler erkannt werden, um sie für die Darstellung auf Arithmetik zu reduzieren).

Es macht für mich keinen Sinn anzunehmen, dass alle Aufzählungen eine Gesamtordnung haben oder dass sie zyklisch sind. Ist es angesichts type failure enum { none; input; file; network } wirklich sinnvoll zu erzwingen, dass eine ungültige Eingabe weniger als ein Dateifehler ist oder dass das Erhöhen eines Dateifehlers zu einem Netzwerkfehler führt oder dass das Erhöhen eines Netzwerkfehlers zum Erfolg führt?

Unter der Annahme, dass die primäre Verwendung für zyklisch geordnete Werte besteht, wäre eine andere Möglichkeit, dies zu handhaben, das Erstellen einer neuen Klasse von parametrisierten Integer-Typen. Dies ist eine schlechte Syntax, aber nehmen wir zur Diskussion an, es ist I%N , wobei I ein ganzzahliger Typ und N eine ganzzahlige Konstante ist. Alle Arithmetik mit einem Wert dieses Typs ist implizit mod N. Dann könnten Sie tun

type Weekday uint%7
const (
  Sunday Weekday = iota
  //etc.

also Samstag + 1 == Sonntag und Wochentag(456) == Montag. Es ist unmöglich, einen ungültigen Wochentag zu konstruieren. Es könnte jedoch außerhalb von const/iota nützlich sein.

Denn wenn Sie überhaupt nicht wollen, dass es zahlreich ist, wie @ianlancetaylor betonte, was ich wirklich will, sind Summentypen.

Die Einführung eines beliebigen modularen Arithmetiktyps ist ein interessanter Vorschlag. Dann könnten Aufzählungen diese Form haben, wodurch Sie eine triviale String-Methode erhalten:

var Weekdays = [...]string{"Sunday", ..., "Saturday"}

type Weekday = uint % len(Weekdays)

In Kombination mit Ints beliebiger Größe erhalten Sie auch Int128, Int256 usw.

Sie könnten auch einige eingebaute Elemente definieren:

type uint8 = uint%(1<<8)
// etc

Der Compiler kann mehr Grenzen nachweisen als zuvor. Und APIs können präzisere Zusicherungen über Typen bereitstellen, z. B. kann die Funktion Len64 in math/bits jetzt uint % 64 zurückgeben.

Bei der Arbeit an einem RISC-V-Port wollte ich einen uint12 -Typ, da meine Befehlscodierungskomponenten 12 Bit sind; das hätte uint % (1<<12) sein können. Viele Bit-Manipulationen, insbesondere Protokolle, könnten davon profitieren.

Die Nachteile sind natürlich erheblich. Go neigt dazu, Code gegenüber Typen zu bevorzugen, und das ist typlastig. Operationen wie + und - können plötzlich so teuer wie % werden. Ohne irgendeine Art von Typparametrik müssen Sie wahrscheinlich in das kanonische uint8 , uint16 usw. konvertieren, um mit fast jeder Bibliotheksfunktion zu interagieren, und die Rückkonvertierung kann Grenzen verbergen Fehler (es sei denn, wir haben eine Möglichkeit, eine Panik-on-Out-of-Range-Konvertierung durchzuführen, die ihre eigene Komplexität mit sich bringt). Und ich kann sehen, dass es überstrapaziert wird, zB mit uint % 1000 für HTTP-Statuscodes.

Trotzdem eine interessante Idee. :)


Andere kleine Antworten:

Das ist eine Art Durchsickern der Intigkeit

Das lässt mich denken, dass sie wirklich ints sind. :)

Gängige Muster können einfach mit go generate ausgefüllt werden

Wenn Sie sowieso Code mit Aufzählungen generieren müssen, können Sie meiner Meinung nach auch String-Funktionen und Begrenzungsprüfungen und dergleichen generieren und Aufzählungen mit Codegenerierung anstelle des Gewichts der Sprachunterstützung durchführen.

Es macht für mich keinen Sinn anzunehmen, dass alle Aufzählungen eine Gesamtordnung haben oder dass sie zyklisch sind.

Fair genug. Das lässt mich denken, dass eine Handvoll konkreter Anwendungsfälle dazu beitragen würde, Klarheit darüber zu schaffen, was wir von Aufzählungen erwarten. Ich vermute irgendwie, dass es keine klaren Anforderungen geben wird und dass die Emulation von Aufzählungen mit anderen Sprachkonstruktionen (dh dem Status quo) am Ende am sinnvollsten ist. Aber das ist nur eine Hypothese.

Restringifizierung, was @creker gesagt hat.

Fair genug. Aber ich frage mich, wie viele Fälle am Ende wie Wochentage sind. Alles, was dem Benutzer zugewandt ist, ist sicher. Und die Stringifizierung scheint eine der Hauptanforderungen für Aufzählungen zu sein.

@josharian Enums, die wirklich ints sind, würden jedoch wahrscheinlich einen ähnlichen Mechanismus benötigen. Ansonsten, was ist enum { A; B; C}(42) ?

Sie können sagen, dass es sich um einen Compilerfehler handelt, aber das funktioniert nicht in komplizierterem Code, da Sie zur Laufzeit in und aus ints konvertieren können.

Es ist entweder A oder eine Laufzeitpanik. In beiden Fällen fügen Sie einen integralen Typ mit einer begrenzten Domäne hinzu. Wenn es sich um eine Laufzeitpanik handelt, fügen Sie einen integralen Typ hinzu, der bei einem Überlauf in Panik gerät, wenn die anderen umlaufen. Wenn es A ist, haben Sie uint%N mit einer gewissen Zeremonie hinzugefügt.

Die andere Option ist, es weder A, B noch C sein zu lassen, aber das haben wir heute mit const/iota, also gibt es keine Gewinne.

Alle Gründe, aus denen Sie sagen, dass int%N es nicht in die Sprache schafft, scheinen gleichermaßen für Aufzählungen zu gelten, die irgendwie ints sind. (Obwohl ich in keiner Weise böse wäre, wenn so etwas enthalten wäre).

Das Wegnehmen der Int-iness beseitigt dieses Rätsel. Es erfordert Code-Generierung für Fälle, in denen etwas von dieser Int-iness wieder hinzugefügt werden soll, aber es gibt Ihnen auch die Wahl, dies nicht zu tun, wodurch Sie steuern können, wie viel Int-iness eingeführt werden soll und von welcher Art: Sie können no hinzufügen " next"-Methode, eine zyklische next-Methode oder eine next-Methode, die einen Fehler zurückgibt, wenn Sie über den Rand fallen. (Sie enden auch nicht damit, dass Sachen wie Monday*Sunday - Thursday legal sind). Die zusätzliche Steifigkeit macht es zu einem formbareren Baumaterial. Eine diskriminierte Union modelliert schön die Nicht-int-y-Varietät: pick { A, B, C struct{} } , unter anderem.

Die Hauptvorteile solcher Informationen in der Sprache sind folgende

  1. Illegale Werte sind illegal.
  2. Die Informationen sind verfügbar, um zu reflektieren und zu gehen/Typen, sodass Programme darauf reagieren können, ohne Annahmen oder Anmerkungen machen zu müssen (die derzeit nicht zum Reflektieren verfügbar sind).

Die Hauptvorteile solcher Informationen in der Sprache sind: Illegale Werte sind illegal.

Ich denke, es ist wichtig zu betonen, dass nicht jeder dies als Vorteil sieht. Ich sicherlich nicht. Es macht es oft einfacher, Werte zu konsumieren, es macht es oft schwieriger, sie zu produzieren. Was Sie schwerer wiegen, scheint bisher von Ihren persönlichen Vorlieben abzuhängen. Somit stellt sich auch die Frage, ob es sich insgesamt um einen Nettonutzen handelt.

Ich sehe auch keinen Sinn darin, illegale Werte zu verbieten. Wenn Sie bereits Mittel haben, um die Gültigkeit selbst zu überprüfen (wie in meinem obigen Vorschlag), welchen Vorteil bringt diese Einschränkung? Für mich macht es die Sache nur komplizierter. In meinen Anwendungen können Aufzählungen in den meisten Fällen ungültige/unbekannte Werte enthalten, und Sie mussten dies je nach Anwendung umgehen - vollständig wegwerfen, auf einen Standardwert herabstufen oder so speichern, wie es ist.

Ich stelle mir vor, dass strenge Aufzählungen, die ungültige Werte verbieten, in sehr begrenzten Fällen nützlich sein könnten, in denen Ihre App von der Außenwelt isoliert ist und keine Möglichkeit hat, eine ungültige Eingabe zu erhalten. Wie interne Aufzählungen, die nur Sie sehen und verwenden können.

const mit iota ist in der Kompilierzeit nicht sicher, die Überprüfung würde zur Laufzeit verzögert, und die sichere Überprüfung erfolgt nicht auf Typebene. Ich denke also, dass iota Enum nicht buchstäblich ersetzen kann, ich bevorzuge Enum, weil es leistungsfähiger ist.

Illegale Werte sind illegal.
Ich denke, es ist wichtig zu betonen, dass nicht jeder dies als Vorteil sieht.

Ich verstehe diese Logik nicht. Typen sind Mengen von Werten. Sie können einer Variablen keinen Typ zuweisen, deren Wert nicht zu diesem Typ gehört. Verstehe ich etwas falsch?

PS: Ich stimme zu, dass Aufzählungen ein Sonderfall von Summentypen sind und dieses Problem Vorrang vor diesem haben sollte.

Lassen Sie es mich anders formulieren/präzisieren: Nicht jeder sieht es als Vorteil an, dass Enums geschlossen sind.

Wenn man so streng sein will, dann ist a) „Illegale Werte sind illegal“ eine Tautologie und kann b) somit nicht als Vorteil gewertet werden. Bei auf Konstanten basierenden Aufzählungen sind Ihrer Interpretation nach auch illegale Werte illegal. Der Typ erlaubt einfach viel mehr Werte.

Wenn Enums Ints sind und jedes Int zulässig ist (aus Sicht des Typsystems), besteht der einzige Gewinn darin, dass die benannten Werte des Typs widergespiegelt werden.

Das ist im Grunde nur const/iota, aber Sie müssen stringer nicht ausführen, da das fmt-Paket die Namen mithilfe von Reflektion abrufen kann. (Sie müssten immer noch stringer ausführen, wenn Sie wollten, dass sich die Zeichenfolgen von den Namen in der Quelle unterscheiden).

@jimmyfrasche Besaitung ist nur ein netter Bonus. Das Hauptmerkmal für mich ist, wie Sie in meinem obigen Vorschlag lesen können, die Möglichkeit, zur Laufzeit zu überprüfen, ob der angegebene Wert ein gültiger Wert des angegebenen Enum-Typs ist.

Zum Beispiel bei so etwas

type Foo enum {
    Val1 = 1
    Val2 = 2
}

Und Reflexionsmethode wie

func IsValidEnum(v {}interface) bool

Wir könnten so etwas machen

a := Foo.Val1
b := Foo(-1)
reflection.IsValidEnum(a) //returns true
reflection.IsValidEnum(b)  //returns false

Als Beispiel aus der Praxis können Sie sich Aufzählungen in C# ansehen, die meiner Meinung nach diesen Mittelweg perfekt eingefangen haben, anstatt blind dem zu folgen, was Java getan hat. Um die Gültigkeit in C# zu überprüfen, verwenden Sie die statische Methode Enum.IsDefined .

@crecker Der einzige Unterschied zwischen dem und const/iota ist der
in Reflect gespeicherte Informationen. Das ist nicht viel Gewinn für ein Ganzes
neue Art von Typ.

Etwas verrückte Idee:

Speichern Sie die Namen und Werte für alle Konstanten, die im selben Paket deklariert sind
als ihr definierter Typ in einer Weise, die reflektiert werden kann. Es wäre
Es ist jedoch seltsam, diese enge Klasse der const-Verwendung herauszugreifen.

Das Hauptmerkmal für mich, wie Sie in meinem Vorschlag oben lesen können

IMO verdeutlicht dies eines der Hauptprobleme, die diese Diskussion in die Länge ziehen: Ein Mangel an Klarheit darüber, was die "Hauptmerkmale" sind. Da scheint jeder etwas andere Vorstellungen zu haben.
Ich persönlich mag immer noch das Format von Erfahrungsberichten, um dieses Set zu entdecken. Es gibt sogar einen in der Liste (obwohl ich persönlich noch die Tatsache anmerken würde, dass der Abschnitt "Was schief gelaufen ist" nur erwähnt, was schief gehen könnte, nicht was tatsächlich passiert ist ). Vielleicht wäre es hilfreich, ein paar hinzuzufügen, die veranschaulichen, wo das Fehlen einer Typprüfung zu Ausfällen/Fehlern oder zB zum Versäumnis führt, groß angelegte Refactorings durchzuführen.

@jimmyfrasche aber das löst ein großes Problem in vielen Anwendungen - die Validierung von Eingabedaten. Ohne Hilfe des Typsystems müssen Sie es von Hand tun, und das ist nicht etwas, was Sie in ein paar Zeilen Code tun könnten. Eine Art typunterstützte Validierung würde das lösen. Das Hinzufügen von Zeichenfolgen darüber würde die Protokollierung vereinfachen, da Sie ordnungsgemäß formatierte Namen und nicht die zugrunde liegenden Typwerte hätten.

Auf der anderen Seite würde eine strenge Aufzählung die möglichen Anwendungsfälle stark einschränken. Jetzt können Sie sie beispielsweise nicht mehr einfach in Protokollen verwenden. Um sogar ungültige Werte beizubehalten, müssten Sie Aufzählungen löschen und einfache Werttypen verwenden und sie möglicherweise später in Aufzählungen konvertieren, wenn Sie dies benötigen. In einigen Fällen könnten Sie den ungültigen Wert löschen und einen Fehler auslösen. In anderen können Sie auf einen Standardwert herabstufen. In jedem Fall kämpfen Sie mit Einschränkungen Ihres Typsystems, anstatt dass es Ihnen hilft, Fehler zu vermeiden.

Sehen Sie sich nur an, was protobuf für Java generieren muss, um Java-Enumerationen zu umgehen.

@Merovius in Bezug auf die Validierung, ich glaube, ich habe das bereits mehrfach behandelt. Ich weiß nicht, was noch hinzugefügt werden könnte, außer - ohne Validierung müssen Sie riesige Mengen an ziemlich viel Copy-Paste-Code schreiben, um Ihre Eingabe zu validieren. Das Problem ist offensichtlich, ebenso wie die vorgeschlagene Lösung dabei helfen könnte. Ich arbeite nicht an einer groß angelegten Anwendung, die jeder kennt, aber Fehler in diesem Validierungscode haben mich oft genug in mehreren Sprachen mit dem gleichen Konzept von Aufzählungen gebissen, dass ich etwas dagegen tun möchte.

Auf der anderen Seite sehe ich kein Argument (Entschuldigung, wenn ich etwas verpasst habe) für die Implementierung von Aufzählungen, die keine ungültigen Werte zulassen. Theoretisch ist es nett und ordentlich, aber ich sehe einfach nicht, dass es mir in realen Anwendungen hilft.

Es gibt nicht so viele Funktionen, die die Leute von Aufzählungen erwarten. Stringifizierung, Validierung, streng/locker in Bezug auf ungültige Werte, Aufzählung - das ist so ziemlich alles, was ich sehen kann. Jeder (einschließlich mir natürlich) mischt sie an dieser Stelle einfach herum. streng/locker scheint aufgrund ihrer widersprüchlichen Natur der Hauptstreitpunkt zu sein. Ich glaube nicht, dass alle dem einen oder anderen zustimmen werden. Vielleicht könnte die Lösung darin bestehen, beide irgendwie zu integrieren und den Programmierer wählen zu lassen, aber ich kenne keine Sprachen, die das haben, um zu sehen, wie es in der realen Welt funktionieren könnte.

@crecker mein Vorschlag, die Konstanten in den Exportdaten oben zu speichern
die Umstände die Art von Dingen zulassen würden, um die Sie bitten
ohne die Einführung einer neuen Art von Typ.

Ich bin mir nicht sicher, ob dies der idiomatische Weg ist, und ich bin auch ziemlich neu in der Sprache, aber das Folgende funktioniert und ist prägnant

type Day struct {
    value string
}

// optional, if you need string representation
func (d Day) String() string { return d.value }

var (
    Monday = Day{"Monday"}
    Tuesday = Day{"Tuesday"}
)

func main() {
    getTask(Monday)
}

func getTask(d Day) string {
    if d == Monday {
        fmt.Println("today is ", d, "!”) // today is Monday !
        return "running"
    }

    return "nothing to do"
}

Vorteile :

Nachteile :

Brauchen wir wirklich Aufzählungen?

Was hält jemanden davon ab, so etwas zu tun:

NotADay := Day{"NotADay"}
getTask(NotADay)

Der Konsument einer solchen Variablen kann dies mit einer ordnungsgemäßen Überprüfung der erwarteten Werte erkennen oder auch nicht (vorausgesetzt, es gibt keine schlechten Annahmen in Switch-Anweisungen, wie zum Beispiel alles, was nicht Samstag oder Sonntag ist, ein Wochentag ist), aber das wäre nicht der Fall bis zur Laufzeit. Ich denke, man würde es vorziehen, wenn diese Art von Fehler zur Kompilierungszeit abgefangen wird, nicht zur Laufzeit.

@bpkroth
Indem ich Day in einem eigenen Paket habe und nur ausgewählte Felder und Methoden verfügbar mache, kann ich keine neuen Werte vom Typ Day außerhalb von package day erstellen
Außerdem kann ich auf diese Weise keine anonymen Strukturen an getTask übergeben

./Tag/Tag.gehen

package day

type Day struct {
    value string
}

func (d Day) String() string { return d.value }

var (
    Monday  = Day{"Monday"}
    Tuesday = Day{"Tuesday"}
    Days    = []Day{Monday, Tuesday}
)

./main.go

package main

import (
    "fmt"
    "github.com/somePath/day"
)

func main() {
    january := day.Day{"january"} // implicit assignment of unexported field 'value' in day.Day literal

    var march struct {
        value string
    }
    march.value = "march"
    getTask(march) // cannot use march (type struct { value string }) as type day.Day in argument to getTask

    getTask(day.Monday)
}

func getTask(d day.Day) string {
    if d == day.Monday {
        fmt.Println("today is ", d, "!") // today is Monday !
        return "running"
    }

    return "nothing to do"
}

func iterateDays() {
    for _, d := range day.Days {
        fmt.Println(d)
    }
}

Ich habe in meinem ganzen Leben noch keine andere Sprache gesehen, die darauf besteht, die einfachsten und hilfreichsten Funktionen wie Aufzählungen, ternäre Operatoren, Kompilierung mit unbenutzten Variablen, Summentypen, Generika, Standardparameter usw. nicht hinzuzufügen ...

Ist Golang ein soziales Experiment, um zu sehen, wie dumm Entwickler sein können?

@gh67uyyghj Jemand hat deinen Kommentar als nicht zum Thema gehörend markiert! und ich denke, jemand wird dasselbe mit meiner Antwort tun. aber ich denke, die Antwort auf Ihre Frage lautet JA. In GoLang bedeutet funktionslos zu sein, funktionsreich zu sein, also ist alles, was GoLang nicht hat, tatsächlich eine Funktion, die GoLang hat, die andere Programmiersprachen nicht haben!!

@L-oris Dies ist eine sehr interessante Möglichkeit, Aufzählungen mit Typen zu implementieren. Aber es fühlt sich unangenehm an, und ein Aufzählungsschlüsselwort (das die Sprache notwendigerweise noch komplizierter macht) würde es einfacher machen:

  • schreiben
  • lesen
  • Grund über

In Ihrem Beispiel (was großartig ist, weil es heute funktioniert) impliziert das Vorhandensein von Aufzählungen (in irgendeiner Form) die Notwendigkeit:

  • Erstellen Sie einen Strukturtyp
  • Erstellen Sie eine Methode
  • Variablen erstellen (nicht einmal Konstanten, obwohl ein Benutzer der Bibliothek diese Werte nicht ändern kann)

Dies dauert länger (wenn auch nicht _so_ viel länger), um es zu lesen, zu schreiben und darüber nachzudenken (beachten Sie, dass es Aufzählungen darstellt und als solche verwendet werden sollte).

Daher denke ich, dass der Syntaxvorschlag in Bezug auf Einfachheit und Mehrwert für die Sprache den richtigen Ton trifft.

Danke @andradei
Ja, es ist eine Problemumgehung, aber ich denke, das Ziel der Sprache ist es, sie klein und einfach zu halten
Wir könnten auch argumentieren, dass wir den Unterricht vermissen, aber dann lass uns einfach zu Java wechseln :)

Ich würde mich eher auf Go 2 Vorschläge konzentrieren, bessere Fehlerbehandlung zB. würde mir viel mehr Wert bieten als diese Aufzählungen

Zurück zu deinen Punkten:

  • es ist nicht so viel Boilerplate; im schlimmsten Fall können wir einige Generatoren haben (aber ist es wirklich so viel Code?)
  • Wie viel „Einfachheit“ erreichen wir, wenn wir ein neues Schlüsselwort hinzufügen, und wie viele spezifische Verhaltensweisen es wahrscheinlich haben wird?
  • Wenn Sie ein wenig kreativ mit Methoden umgehen, können Sie diesen Aufzählungen auch interessante Fähigkeiten hinzufügen
  • für die Lesbarkeit ist es eher gewöhnungsbedürftig; Fügen Sie vielleicht einen Kommentar darüber hinzu oder stellen Sie Ihren Variablen ein Präfix voran
package day

// Day Enum
type Day struct {
    value string
}

@L-oris Ich verstehe. Ich freue mich auch über Vorschläge für Go 2. Ich würde argumentieren, dass Generika die Komplexität der Sprache mehr erhöhen werden als Aufzählungen. Aber um bei deinen Punkten zu bleiben:

  • Es ist in der Tat nicht so viel Boilerplate
  • Wir müssten überprüfen, wie bekannt das Konzept von Enum ist, um sicher zu sein, ich würde sagen, die meisten Leute wissen, was es ist (aber ich kann das nicht beweisen). Die Komplexität der Sprache wäre zu einem guten "Preis" für ihre Vorteile zu zahlen.
  • Das ist wahr, keine Aufzählungen zu haben sind Probleme, die mir nur aufgefallen sind, als ich generetad protobuf-Code überprüfte und zum Beispiel versuchte, ein Datenbankmodell zu erstellen, das eine Aufzählung nachahmt.
  • Das stimmt auch.

Ich habe viel über diesen Vorschlag nachgedacht, und ich kann sehen, welchen großen Wert Einfachheit für die Produktivität hat und warum Sie dazu tendieren, ihn beizubehalten, es sei denn, eine Änderung ist eindeutig erforderlich. Enums könnten auch die Sprache so drastisch verändern, dass es nicht mehr Go ist, und die Bewertung der Vor- und Nachteile scheint lange zu dauern. Daher dachte ich, dass einfache Lösungen wie Ihre, bei denen der Code noch gut lesbar ist, zumindest vorerst eine gute Lösung sind.

Leute, wirklich wollen diese Funktion für die Zukunft!. Pointer und die Art, " Enumerationen " in _heutzutage_ zu definieren, vertragen sich nicht sehr gut. Zum Beispiel: https://play.golang.org/p/A7rjgAMjfCx

Mein Vorschlag für enum folgt. Wir sollten dies als einen neuen Typ betrachten. Zum Beispiel möchte ich einen Enum-Typ mit beliebiger Struktur und folgender Implementierung verwenden:

package application

type Status struct {
Name string
isFinal bool
}

enum Status {
     Started = &Status{"Started",false}
     Stopped = &Status{"Stopped",true}
     Canceled = &Status{"Canceled",true}
}

// application.Status.Start - to use

Es ist verständlich, wie man diese Struktur zusammenstellt und wie man arbeitet und wie man auf Schnur umstellt und so weiter.
Und natürlich wäre es toll, wenn ich die „Next“-Funktion überschreiben könnte.

Dafür müsste Go zuerst tiefe unveränderliche Strukturen unterstützen. Ohne unveränderliche Typen kann ich mir vorstellen, dass Sie dies mit Aufzählungen tun könnten, um dasselbe zu haben:

type Status enum {
  Started
  Stopped
}

func isFinal(s Status) bool {
  exhaustive switch(s) {
    case Started: return false;
    case Stopped: return true;
  }
}

Ich denke, es sollte einfacher aussehen

func isFinal(s Status) bool {
  return s == Status.Stopped
}

Vorschlag für Go2

Logischerweise sollen Enumerationen eine Typschnittstelle bereitstellen.
Ich habe angegeben, dass frühere Aufzählungen getrennt sein sollen.
Es handelt sich um explizit benannte Konstanten, die an einen bestimmten Namensraum gebunden sind.

enum Status uint8 {
  Started  // Status.Started == 0
  Stopped // Status.Stopped == 1, etc, like we have used iota
}
// or 
enum Status string  {
  Started // Status.Started == "Started", like it works with JSON
  Stopped // Status.Stopped == "Stopped", etc
}
// unless you wanna define its values explicitly
enum Status {
  Started "started"  // compiler can infer underlying type
  Stopped "finished"
}
// and enums are type extensions and should be used like this
type MyStatus Status

MyStatus validatedStatus // holds a nil until initialized

// for status value validation we can use map pattern
if validatedStatus, ok := MyStatus[s]; ok {
  // this value is a valid status
  // and we can use it later as regular read-only string
  // or like this
  if validatedStatus == MyStatus.Started {
     fmt.Printf("Hey, my status is %s", validatedStatus)
  }
}

Enums sind Typerweiterungen, "Konstantencontainer".

Für Typenliebhaber

Syntaxalternativen für diejenigen, die es als Typ sehen möchten

type Status uint8 enum {
  Started  // Status.Started == 0
  Stopped // Status.Stopped == 1, etc, like we have used iota
}

Aber wir können diese expliziten Top-Level-Deklarationen auch vermeiden

type Status enum {
  Started  // Status.Started == 0
  Stopped // Status.Stopped == 1, etc, like we have used iota
}

Validierungsbeispiel bleibt gleich.

aber für den Fall

type Status1 uint8 enum {
  Started  // Status1.Started == 0
  Stopped // Status1.Stopped == 1, etc, like we have used iota
}

type Status2 uint8 enum {
  Started  // Status1.Started == 0
  Stopped // Status1.Stopped == 1, etc, like we have used iota
}

Wie sieht es mit Status1.Started == Status2.Started aus?
über Rangieren?

Wenn ich eine Stelle ändere?

type Status uint8 enum {
  Started  // Status.Started == 0
  InProcess
  Stopped // Status.Stopped == 1, etc, like we have used iota
}

Ich stimme @Goodwine über unveränderliche Typen zu.

Rangieren ist eine interessante Frage.
Dies hängt alles davon ab, wie wir den zugrunde liegenden Wert behandeln werden. Wenn wir tatsächliche Werte verwenden, wäre Status1.Started gleich Status2.Started .
Wenn wir mit symbolischer Interpretation gehen, würden diese als unterschiedliche Werte betrachtet.

Das Einfügen von etwas bewirkt eine Änderung der Werte (genauso wie bei iota ).
Um dies zu vermeiden, muss der Entwickler Werte zusammen mit Deklarationen angeben.

type Status uint8 enum {
  Started  0
  InProcess 2
  Stopped 1
}

Das ist eine offensichtliche Sache.
Wenn wir solche Probleme vermeiden wollen, müssen wir eine vorhersagbare Compilerausgabe basierend auf der lexikalischen Interpretation der Aufzählungswerte bereitstellen. Ich nehme den einfachsten Weg an - das Erstellen einer Hash-Tabelle oder das Festhalten an symbolischen Namen (Strings), es sei denn, es ist eine benutzerdefinierte Typumwandlung definiert.

Mir gefällt, wie Rust Enums implementiert.

Standard ohne angegebenen Typ

enum IpAddr {
    V4,
    V6,
}

Benutzerdefinierter Typ

enum IpAddr {
    V4(string),
    V6(string),
}

home := IpAddr.V4("127.0.0.1");
loopback := IpAddr.V6("::1");

Komplexe Typen

enum Message {
    Quit,
    Move { x: int32, y: int32 },
    Write(String),
    ChangeColor(int32, int32, int32),
}

Sicherlich wäre es großartig, auch einfache Aufzählungen wie in C # zu haben, die als ganzzahlige Typen gespeichert werden.

Die oben genannten gehen über enum hinaus, das sind _diskriminierte Vereinigungen_, die in der Tat mächtiger sind, insbesondere mit _Musterabgleich_, was eine kleine Erweiterung von switch sein könnte, etwa so:

switch something.(type) {
case Quit:
        ...
case ChangeColor; r, g, b := something:
        ...
case Write: // Here `something` is known to be a string
        ...
// Ideally Go would warn here about the missing case for "Move"
}

Ich brauche keine Kompilierzeitüberprüfungen von Enumerationen, da dies wie erwähnt gefährlich sein könnte

Was ich mehrmals gebraucht hätte, wäre gewesen, alle Konstanten eines bestimmten Typs zu durchlaufen:

  • entweder zur Validierung (wenn wir uns sehr sicher sind, dass wir dies nur akzeptieren wollen oder um unbekannte Optionen einfach zu ignorieren)

    • oder für eine Liste möglicher Konstanten ( denken Sie an Dropdowns ).

Wir könnten die Validierung mit iota durchführen und das Ende der Liste angeben. Die Verwendung von iota für etwas anderes als nur innerhalb des Codes wäre jedoch ziemlich gefährlich, da Dinge kaputt gehen, wenn eine Konstante in der falschen Zeile eingefügt wird (ich weiß, wir müssen uns bewusst sein, wo wir Dinge in die Programmierung einfügen, aber so ein Fehler ist viel schwerer zu finden als andere Dinge). Außerdem haben wir keine Beschreibung, wofür die Konstante eigentlich steht, wenn es sich um eine Zahl handelt. Das führt zum nächsten Punkt:

Ein nettes Extra wäre es, Stringify-Namen dafür anzugeben.

Was hält jemanden davon ab, so etwas zu tun:

NotADay := Day{"NotADay"}
getTask(NotADay)

Der Konsument einer solchen Variablen kann dies mit einer ordnungsgemäßen Überprüfung der erwarteten Werte erkennen oder auch nicht (vorausgesetzt, es gibt keine schlechten Annahmen in Switch-Anweisungen, wie zum Beispiel alles, was nicht Samstag oder Sonntag ist, ein Wochentag ist), aber das wäre nicht der Fall bis zur Laufzeit. Ich denke, man würde es vorziehen, wenn diese Art von Fehler zur Kompilierungszeit abgefangen wird, nicht zur Laufzeit.

@L-oris Also was ist damit:

package main
import "yet/it/is/not/a/good/practice/in/Go/enum/example/day"

func main()
{
  // var foo day.Day
  foo := day.Day{}
  bar(foo)
}

func bar(day day.Day)
{
  // xxxxxxxxxx
}

Was wir wollen, ist NICHT RUNTIME SILENCE & Weird BUG verursacht durch das [return "nothing to do"] , sondern eine FEHLERMELDUNG zur Kompilierzeit / Codierzeit !
VERSTEHEN?

  1. enum ist in der Tat ein neuer Typ, was type State string tut, es besteht keine idiomatische Notwendigkeit, ein neues Schlüsselwort einzuführen. Bei Go geht es nicht darum, Platz in Ihrem Quellcode zu sparen, sondern um Lesbarkeit und Zweckmäßigkeit.

  2. Mangelnde Typsicherheit, die Verwechslung der neuen string - oder int -basierten Typen mit tatsächlichen Strings/Ints ist die Haupthürde. Alle Enum-Klauseln werden als const deklariert, wodurch eine Reihe bekannter Werte erstellt wird, die der Compiler prüfen kann.

  3. Stringer Schnittstelle ist die Redewendung für die Darstellung eines beliebigen Typs als für Menschen lesbaren Text. Ohne Anpassung ist dies type ContextKey string -Aufzählungen der Zeichenfolgenwert und bei iota -generierten Aufzählungen ist es die Ganzzahl, ähnlich wie XHR-ReadyState-Codes (0 – nicht gesendet, 4 – erledigt) in JavaScript.

    Das Problem liegt vielmehr in der Fehlbarkeit der benutzerdefinierten func (k ContextKey) String() string -Implementierung, die normalerweise mit einem Schalter erfolgt, der jede bekannte Konstante der Aufzählungsklausel enthalten muss.

  4. In einer Sprache wie Swift gibt es eine Vorstellung von _einem erschöpfenden Schalter_. Dies ist ein guter Ansatz sowohl für die Typprüfung anhand einer Menge von const s als auch für den Aufbau einer idiomatischen Methode zum Aufrufen dieser Prüfung. Die String() -Funktion, die eine häufige Notwendigkeit ist, ist ein großartiger Fall für die Implementierung.

Vorschlag

package main

import (
    "context"
    "strconv"
    "fmt"
    "os"
)

// State is an enum of known system states.
type DeepThoughtState int

// One of known system states.
const (
    Unknown DeepThoughtState = iota
    Init
    Working
    Paused
    ShutDown
)

// String returns a human-readable description of the State.
//
// It switches over const State values and if called on
// variable of type State it will fall through to a default
// system representation of State as a string (string of integer
// will be just digits).
func (s DeepThoughtState) String() string {
    // NEW: Switch only over const values for State
    switch s.(const) {
    case Unknown:
        return fmt.Printf("%d - the state of the system is not yet known", Unknown)
    case Init:
        return fmt.Printf("%d - the system is initializing", Init)
    } // ERR: const switch must be exhaustive; add all cases or `default` clause

    // ERR: no return at the end of the function (switch is not exhaustive)
}

// RegisterState allows changing the state
func RegisterState(ctx context.Context, state string) (interface{}, error) {
    next, err := strconv.ParseInt(state, 10, 32)
    if err != nil {
        return nil, err
    }
    nextState := DeepThoughtState(next)

    fmt.Printf("RegisterState=%s\n", nextState) // naive logging

        // NEW: Check dynamically if variable is a known constant
    if st, ok := nextState.(const); ok {
        // TODO: Persist new state
        return st, nil
    } else {
        return nil, fmt.Errorf("unknown state %d, new state must be one of known integers", nextState)
    }
}

func main() {
    _, err := RegisterState(context.Background(), "42")
    if err != nil {
        fmt.Println("error", err)
        os.Exit(1)
    }
    os.Exit(0)
    return
}

PS Assoziierte Werte in Swift-Enumerationen sind einer meiner Lieblingstricks. In Go ist kein Platz für sie. Wenn Sie einen Wert neben Ihren Aufzählungsdaten haben möchten, verwenden Sie ein stark typisiertes struct , das die beiden umschließt.

Vor einigen Monaten habe ich einen Proof-of-Concept für einen Linter geschrieben, der überprüft, ob Aufzählungstypen richtig behandelt werden. https://github.com/loov/enumcheck

Derzeit werden Kommentare verwendet, um Dinge als Aufzählungen zu markieren:

type Letter byte // enumcheck

const (
    Alpha Letter = iota
    Beta
    Gamma
)

func Switch(x Letter) {
    switch x { // error: "missing cases Beta and Gamma"
    case Alpha:
        fmt.Println("alpha")
    case 4: // error: "implicit conversion of 4 to Letter"
        fmt.Println("beta")
    default: // error: "Letter shouldn't have a default case"
        fmt.Println("default")
    }
}

Ich habe mich damit beschäftigt, herauszufinden, wie ich mit all den impliziten Konvertierungen umgehen soll, aber es funktioniert anständig für einfache Fälle.

Beachten Sie, dass es derzeit noch in Arbeit ist, sodass sich die Dinge ändern können. zB könnte es anstelle von Kommentaren ein Stub-Paket verwenden, um die Typen zu kommentieren, aber Kommentare sind im Moment gut genug.

Die aktuelle Implementierung von Enums in Go1 ist die seltsamste und unauffälligste Enum-Implementierung in einer mir bekannten Sprache. Sogar C implementiert sie schöner. Das Iota-Ding sieht aus wie ein Hack. Und was zum Teufel bedeutet iota überhaupt? Wie soll ich mir dieses Schlüsselwort merken? Go soll einfach zu erlernen sein. Aber das ist nur skurril.

@pofl :
Ich stimme zwar zu, dass Go-Enumerationen ziemlich umständlich sind, aber iota ist eigentlich nur ein normales englisches Wort:

Jota
_Substantiv_

  1. eine sehr kleine Menge; Jota; weiß.
  2. der neunte Buchstabe des griechischen Alphabets (I, ι).
  3. der durch diesen Buchstaben repräsentierte Vokal.

Vermutlich wollten sie Definition eins in Bezug auf die Verwendung in der Sprache.

Als Randbemerkung zu einem älteren Kommentar hier drin:
Auch wenn ich diskriminierte Gewerkschaften in Go wirklich gerne hätte, denke ich, dass sie von tatsächlichen Aufzählungen getrennt sein sollten. Mit der Art und Weise, wie Generika derzeit vorgehen, erhalten Sie möglicherweise etwas, das diskriminierten Vereinigungen über Typlisten in Schnittstellen sehr ähnlich ist. Siehe #41716.

Die Verwendung von iota in Go basiert lose auf seiner Verwendung in APL. Zitat von https://en.wikipedia.org/wiki/Iota :

In einigen Programmiersprachen (z. B. A+, APL, C++[6], Go[7]) wird iota (entweder als Kleinbuchstabensymbol ⍳ oder als Bezeichner iota) verwendet, um ein Array aufeinanderfolgender Ganzzahlen darzustellen und zu generieren. Zum Beispiel ergibt ⍳4 in APL 1 2 3 4.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen