Was ist eine idiomatische Art, Enums in Go darzustellen?


522

Ich versuche, ein vereinfachtes Chromosom darzustellen, das aus N Basen besteht, von denen jede nur eine sein kann {A, C, T, G}.

Ich möchte die Einschränkungen mit einer Aufzählung formalisieren, aber ich frage mich, was die idiomatischste Art ist, eine Aufzählung zu emulieren, in Go.


4
In go-Standardpaketen werden sie als Konstanten dargestellt. Siehe golang.org/pkg/os/#pkg-constants
Denys Séguret



7
@icza Diese Frage wurde 3 Jahre zuvor gestellt. Dies kann kein Duplikat davon sein, vorausgesetzt, der Zeitpfeil funktioniert.
Carbokation

Antworten:


658

Zitat aus den Sprachspezifikationen: Iota

Innerhalb einer konstanten Deklaration repräsentiert der vordeklarierte Bezeichner iota aufeinanderfolgende untypisierte Ganzzahlkonstanten. Es wird auf 0 zurückgesetzt, wenn das reservierte Wort const in der Quelle erscheint und nach jeder ConstSpec erhöht wird. Es kann verwendet werden, um eine Reihe verwandter Konstanten zu konstruieren:

const (  // iota is reset to 0
        c0 = iota  // c0 == 0
        c1 = iota  // c1 == 1
        c2 = iota  // c2 == 2
)

const (
        a = 1 << iota  // a == 1 (iota has been reset)
        b = 1 << iota  // b == 2
        c = 1 << iota  // c == 4
)

const (
        u         = iota * 42  // u == 0     (untyped integer constant)
        v float64 = iota * 42  // v == 42.0  (float64 constant)
        w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0 (iota has been reset)
const y = iota  // y == 0 (iota has been reset)

In einer ExpressionList ist der Wert jedes Jota gleich, da er nur nach jeder ConstSpec inkrementiert wird:

const (
        bit0, mask0 = 1 << iota, 1<<iota - 1  // bit0 == 1, mask0 == 0
        bit1, mask1                           // bit1 == 2, mask1 == 1
        _, _                                  // skips iota == 2
        bit3, mask3                           // bit3 == 8, mask3 == 7
)

In diesem letzten Beispiel wird die implizite Wiederholung der letzten nicht leeren Ausdrucksliste ausgenutzt.


Ihr Code könnte also so sein

const (
        A = iota
        C
        T
        G
)

oder

type Base int

const (
        A Base = iota
        C
        T
        G
)

Wenn Sie möchten, dass Basen ein separater Typ von int sind.


16
Tolle Beispiele (ich habe mich nicht an das genaue Iota-Verhalten - wenn es inkrementiert ist - aus der Spezifikation erinnert). Persönlich mag ich es, einer Aufzählung einen Typ zu geben, damit er als Argument, Feld usw.
typgeprüft werden kann

16
Sehr interessant @jnml. Aber ich bin ein bisschen enttäuscht, dass die statische Typprüfung locker zu sein scheint. Zum Beispiel hindert mich nichts daran, die Basis Nr. 42 zu verwenden, die es nie gab: play.golang.org/p/oH7eiXBxhR
Deleplace

4
Go hat kein Konzept für numerische Unterbereichstypen, wie es beispielsweise Pascal getan hat, und Ord(Base)ist daher nicht auf 0..3den zugrunde liegenden numerischen Typ beschränkt, sondern hat dieselben Grenzen. Es ist eine Wahl für das Sprachdesign, ein Kompromiss zwischen Sicherheit und Leistung. Berücksichtigen Sie beim Berühren eines Baseeingegebenen Werts jedes Mal "sichere" Überprüfungen zur Laufzeit . Oder wie soll man das Überlaufverhalten von BaseWert für Arithmetik und für ++und definieren --? Etc.
zzzz

7
Um jnml auch semantisch zu ergänzen, sagt nichts in der Sprache, dass die als Base definierten Konstanten den gesamten Bereich der gültigen Base darstellen, sondern nur, dass diese bestimmten Konstanten vom Typ Base sind. Weitere Konstanten könnten auch an anderer Stelle als Base definiert werden, und dies schließt sich nicht einmal gegenseitig aus (z. B. könnte const Z Base = 0 definiert werden und wäre gültig).
Mna

10
Sie können verwenden, iota + 1um nicht bei 0 zu beginnen.
Marçal Juan

87

In Bezug auf die Antwort von jnml können Sie neue Instanzen des Basistyps verhindern, indem Sie den Basistyp überhaupt nicht exportieren (dh in Kleinbuchstaben schreiben). Bei Bedarf können Sie eine exportierbare Schnittstelle erstellen, die über eine Methode verfügt, die einen Basistyp zurückgibt. Diese Schnittstelle könnte in Funktionen von außen verwendet werden, die sich mit Basen befassen, d. H.

package a

type base int

const (
    A base = iota
    C
    T
    G
)


type Baser interface {
    Base() base
}

// every base must fulfill the Baser interface
func(b base) Base() base {
    return b
}


func(b base) OtherMethod()  {
}

package main

import "a"

// func from the outside that handles a.base via a.Baser
// since a.base is not exported, only exported bases that are created within package a may be used, like a.A, a.C, a.T. and a.G
func HandleBasers(b a.Baser) {
    base := b.Base()
    base.OtherMethod()
}


// func from the outside that returns a.A or a.C, depending of condition
func AorC(condition bool) a.Baser {
    if condition {
       return a.A
    }
    return a.C
}

Das a.BaserHauptpaket ist jetzt praktisch wie eine Aufzählung. Nur innerhalb eines Pakets können Sie neue Instanzen definieren.


10
Ihre Methode scheint perfekt für Fälle zu sein, in denen basesie nur als Methodenempfänger verwendet wird. Wenn Ihr aPaket eine Funktion mit einem Parameter vom Typ verfügbar macht base, wird dies gefährlich. In der Tat könnte der Benutzer es einfach mit dem Literalwert 42 aufrufen, den die Funktion akzeptieren würde, baseda er in ein int umgewandelt werden kann. Um dies zu verhindern, machen Sie baseein struct: type base struct{value:int}. Problem: Sie können keine Basen mehr als Konstanten deklarieren, sondern nur noch Modulvariablen. Aber 42 werden niemals zu einem basesolchen Typ gegossen .
Niriel

6
@metakeule Ich versuche Ihr Beispiel zu verstehen, aber Ihre Wahl in Variablennamen hat es außerordentlich schwierig gemacht.
anon58192932

1
Dies ist einer meiner Bugbears in Beispielen. FGS, mir ist klar, dass es verlockend ist, aber benenne die Variable nicht so wie den Typ!
Graham Nicholls

27

Sie können es so machen:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

Mit diesem Code sollte der Compiler den Aufzählungstyp überprüfen


5
Konstanten werden normalerweise in normalem Kamelfall geschrieben, nicht in Großbuchstaben. Der anfängliche Großbuchstabe bedeutet, dass eine Variable exportiert wird, die möglicherweise Ihren Wünschen entspricht oder nicht.
425nesp

1
Ich habe festgestellt, dass es im Go-Quellcode eine Mischung gibt, bei der manchmal Konstanten in Großbuchstaben und manchmal in Camelcase-Form vorliegen. Haben Sie einen Verweis auf eine Spezifikation?
Jeremy Gailor

@JeremyGailor Ich denke, 425nesp merkt nur an, dass Entwickler sie normalerweise als nicht exportierte Konstanten verwenden, also verwenden Sie camelcase. Wenn der Entwickler feststellt, dass es exportiert werden soll, können Sie alle Groß- oder Kleinbuchstaben verwenden, da keine Präferenz festgelegt ist. Siehe Golang Code Review Empfehlungen und Effective Go Abschnitt über Konstanten
waynethec

Es gibt eine Präferenz. Genau wie Variablen, Funktionen, Typen und andere sollten Konstantennamen MixedCaps oder MixedCaps sein, nicht ALLCAPS. Quelle: Go Code Review Kommentare .
Rodolfo Carvalho

Beachten Sie, dass z. B. Funktionen, die einen MessageType erwarten, gerne untypisierte numerische Konstanten akzeptieren, z. B. 7. Außerdem können Sie jedes int32 in MessageType umwandeln. Wenn Sie sich dessen bewusst sind, denke ich, dass dies der idiomatischste Weg ist.
Kosta vor

23

Es ist wahr, dass die obigen Beispiele für die Verwendung constund iotadie idiomatischsten Arten der Darstellung primitiver Aufzählungen in Go sind. Aber was ist, wenn Sie nach einer Möglichkeit suchen, eine umfassendere Aufzählung zu erstellen, die dem Typ ähnelt, den Sie in einer anderen Sprache wie Java oder Python sehen würden?

Eine sehr einfache Möglichkeit, ein Objekt zu erstellen, das in Python wie eine Zeichenfolgenaufzählung aussieht und sich anfühlt, ist:

package main

import (
    "fmt"
)

var Colors = newColorRegistry()

func newColorRegistry() *colorRegistry {
    return &colorRegistry{
        Red:   "red",
        Green: "green",
        Blue:  "blue",
    }
}

type colorRegistry struct {
    Red   string
    Green string
    Blue  string
}

func main() {
    fmt.Println(Colors.Red)
}

Angenommen, Sie wollten auch einige Dienstprogrammmethoden wie Colors.List()und Colors.Parse("red"). Und Ihre Farben waren komplexer und mussten eine Struktur sein. Dann könnten Sie so etwas machen:

package main

import (
    "errors"
    "fmt"
)

var Colors = newColorRegistry()

type Color struct {
    StringRepresentation string
    Hex                  string
}

func (c *Color) String() string {
    return c.StringRepresentation
}

func newColorRegistry() *colorRegistry {

    red := &Color{"red", "F00"}
    green := &Color{"green", "0F0"}
    blue := &Color{"blue", "00F"}

    return &colorRegistry{
        Red:    red,
        Green:  green,
        Blue:   blue,
        colors: []*Color{red, green, blue},
    }
}

type colorRegistry struct {
    Red   *Color
    Green *Color
    Blue  *Color

    colors []*Color
}

func (c *colorRegistry) List() []*Color {
    return c.colors
}

func (c *colorRegistry) Parse(s string) (*Color, error) {
    for _, color := range c.List() {
        if color.String() == s {
            return color, nil
        }
    }
    return nil, errors.New("couldn't find it")
}

func main() {
    fmt.Printf("%s\n", Colors.List())
}

An diesem Punkt funktioniert es zwar sicher, aber es gefällt Ihnen möglicherweise nicht, wie Sie Farben wiederholt definieren müssen. Wenn Sie dies an dieser Stelle beseitigen möchten, können Sie Tags für Ihre Struktur verwenden und ausgefallene Überlegungen anstellen, um sie einzurichten. Hoffentlich reicht dies jedoch aus, um die meisten Personen abzudecken.


19

Ab Go 1.4 wurde das go generateTool zusammen mit dem stringerBefehl eingeführt, mit dem Ihre Enumeration leicht debuggbar und druckbar ist.


Weißt du, ist eine Gegenlösung. Ich meine Zeichenfolge -> MyType. Da Einweglösung alles andere als ideal ist. Hier ist jdn Kern, der macht, was ich will - aber von Hand zu schreiben ist leicht, Fehler zu machen.
SR

11

Ich bin sicher, wir haben hier viele gute Antworten. Aber ich dachte nur daran, die Art und Weise hinzuzufügen, wie ich aufgezählte Typen verwendet habe

package main

import "fmt"

type Enum interface {
    name() string
    ordinal() int
    values() *[]string
}

type GenderType uint

const (
    MALE = iota
    FEMALE
)

var genderTypeStrings = []string{
    "MALE",
    "FEMALE",
}

func (gt GenderType) name() string {
    return genderTypeStrings[gt]
}

func (gt GenderType) ordinal() int {
    return int(gt)
}

func (gt GenderType) values() *[]string {
    return &genderTypeStrings
}

func main() {
    var ds GenderType = MALE
    fmt.Printf("The Gender is %s\n", ds.name())
}

Dies ist bei weitem eine der idiomatischen Möglichkeiten, wie wir Aufzählungstypen erstellen und in Go verwenden können.

Bearbeiten:

Hinzufügen einer anderen Möglichkeit, Konstanten zum Aufzählen zu verwenden

package main

import (
    "fmt"
)

const (
    // UNSPECIFIED logs nothing
    UNSPECIFIED Level = iota // 0 :
    // TRACE logs everything
    TRACE // 1
    // INFO logs Info, Warnings and Errors
    INFO // 2
    // WARNING logs Warning and Errors
    WARNING // 3
    // ERROR just logs Errors
    ERROR // 4
)

// Level holds the log level.
type Level int

func SetLogLevel(level Level) {
    switch level {
    case TRACE:
        fmt.Println("trace")
        return

    case INFO:
        fmt.Println("info")
        return

    case WARNING:
        fmt.Println("warning")
        return
    case ERROR:
        fmt.Println("error")
        return

    default:
        fmt.Println("default")
        return

    }
}

func main() {

    SetLogLevel(INFO)

}

2
Sie können Konstanten mit Zeichenfolgenwerten deklarieren. IMO ist dies einfacher, wenn Sie sie anzeigen möchten und den numerischen Wert nicht benötigen.
Cbednarski

4

Hier ist ein Beispiel, das sich bei vielen Aufzählungen als nützlich erweisen wird. Es verwendet Strukturen in Golang und stützt sich auf objektorientierte Prinzipien, um sie alle in einem ordentlichen kleinen Bündel zusammenzubinden. Keiner der zugrunde liegenden Codes ändert sich, wenn eine neue Aufzählung hinzugefügt oder gelöscht wird. Der Prozess ist:

  • Definieren Sie eine Aufzählungsstruktur für enumeration items: EnumItem . Es hat einen Integer- und einen String-Typ.
  • Definieren Sie das enumerationals Liste von enumeration items: Enum
  • Erstellen Sie Methoden für die Aufzählung. Einige wurden aufgenommen:
    • enum.Name(index int): Gibt den Namen für den angegebenen Index zurück.
    • enum.Index(name string): Gibt den Namen für den angegebenen Index zurück.
    • enum.Last(): Gibt den Index und den Namen der letzten Aufzählung zurück
  • Fügen Sie Ihre Aufzählungsdefinitionen hinzu.

Hier ist ein Code:

type EnumItem struct {
    index int
    name  string
}

type Enum struct {
    items []EnumItem
}

func (enum Enum) Name(findIndex int) string {
    for _, item := range enum.items {
        if item.index == findIndex {
            return item.name
        }
    }
    return "ID not found"
}

func (enum Enum) Index(findName string) int {
    for idx, item := range enum.items {
        if findName == item.name {
            return idx
        }
    }
    return -1
}

func (enum Enum) Last() (int, string) {
    n := len(enum.items)
    return n - 1, enum.items[n-1].name
}

var AgentTypes = Enum{[]EnumItem{{0, "StaffMember"}, {1, "Organization"}, {1, "Automated"}}}
var AccountTypes = Enum{[]EnumItem{{0, "Basic"}, {1, "Advanced"}}}
var FlagTypes = Enum{[]EnumItem{{0, "Custom"}, {1, "System"}}}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.