Qual è un modo idiomatico di rappresentare gli enum in Go?


523

Sto cercando di rappresentare un cromosoma semplificato, che consiste di N basi, ognuna delle quali può essere solo una {A, C, T, G}.

Vorrei formalizzare i vincoli con un enum, ma mi chiedo quale sia il modo più idiomatico di emulare un enum in Go.


4
In go pacchetti standard sono rappresentati come costanti. Vedi golang.org/pkg/os/#pkg-constants
Denys Séguret



7
@icza Questa domanda è stata posta 3 anni prima. Questo non può essere un duplicato di quello, supponendo che la freccia del tempo sia in ordine.
carbocation

Risposte:


659

Citando le specifiche della lingua: Iota

All'interno di una dichiarazione costante, l'identificatore predeterminato iota rappresenta costanti intere non tipizzate successive. Viene reimpostato su 0 ogni volta che la parola riservata const appare nella sorgente e aumenta dopo ogni ConstSpec. Può essere utilizzato per costruire un insieme di costanti correlate:

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)

All'interno di un ExpressionList, il valore di ogni iota è lo stesso perché viene incrementato solo dopo ogni ConstSpec:

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
)

Questo ultimo esempio sfrutta la ripetizione implicita dell'ultimo elenco di espressioni non vuote.


Quindi il tuo codice potrebbe essere simile

const (
        A = iota
        C
        T
        G
)

o

type Base int

const (
        A Base = iota
        C
        T
        G
)

se si desidera che le basi siano un tipo separato da int.


16
ottimi esempi (non ho ricordato l'esatto comportamento di Iota - quando è incrementato - dalle specifiche). Personalmente mi piace dare un tipo a un enum, quindi può essere controllato se usato come argomento, campo, ecc.
mna

16
@Jnml molto interessante. Ma sono un po 'deluso dal fatto che il controllo statico del tipo sembri essere lento, per esempio nulla mi impedisce di utilizzare la Base n ° 42 che non è mai esistita: play.golang.org/p/oH7eiXBxhR
Deleplace

4
Go non ha il concetto di tipi di sottobrange numerici, come ad esempio quello di Pascal, quindi Ord(Base)non si limita 0..3ma ha gli stessi limiti del tipo numerico sottostante. È una scelta di design del linguaggio, un compromesso tra sicurezza e prestazioni. Quando si tocca un Basevalore digitato, considerare ogni volta i controlli con limiti di tempo "sicuri" . O come si dovrebbe definire il comportamento 'overflow' del Basevalore per l'aritmetica e per ++e --? Ecc.
zzzz,

7
Per completare su jnml, anche semanticamente, nulla nella lingua dice che i cons definiti come Base rappresentano l'intera gamma di Base valida, dice solo che questi particolari consessi sono di tipo Base. Altre costanti potrebbero essere definite altrove come Base e non si escludono nemmeno a vicenda (ad esempio const Z Base = 0 potrebbe essere definita e sarebbe valida).
mna,

10
Puoi usare iota + 1per non iniziare alle 0.
Marçal Juan

87

Facendo riferimento alla risposta di jnml, è possibile impedire nuove istanze del tipo Base non esportando affatto il tipo Base (ovvero scrivendolo in minuscolo). Se necessario, è possibile creare un'interfaccia esportabile con un metodo che restituisce un tipo di base. Questa interfaccia potrebbe essere utilizzata in funzioni esterne che si occupano di Bases, ad es

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
}

All'interno del pacchetto principale a.Baserè effettivamente come un enum ora. Solo all'interno di un pacchetto è possibile definire nuove istanze.


10
Il tuo metodo sembra perfetto per i casi in cui baseviene utilizzato solo come ricevitore del metodo. Se il apacchetto esponesse una funzione che accetta un parametro di tipo base, diventerebbe pericoloso. In effetti, l'utente potrebbe semplicemente chiamarlo con il valore letterale 42, che la funzione accetterebbe come basepoiché può essere trasmesso a un int. Per evitare questo, fare baseun struct: type base struct{value:int}. Problema: non è più possibile dichiarare basi come costanti, solo variabili di modulo. Ma 42 non verrà mai assegnato a uno basedi quel tipo.
Niriel,

6
@metakeule Sto cercando di capire il tuo esempio, ma la tua scelta nei nomi delle variabili ha reso estremamente difficile.
anon58192932,

1
Questo è uno dei miei bugbear negli esempi. FGS, mi rendo conto che è allettante, ma non nominare la variabile uguale al tipo!
Graham Nicholls,

27

Puoi farlo così:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

Con questo codice il compilatore dovrebbe controllare il tipo di enum


5
Le costanti sono di solito scritte in una normale camelcase, non tutte in maiuscolo. La lettera maiuscola iniziale indica che la variabile viene esportata, che può essere o meno ciò che desideri.
425nesp

1
Ho notato nel codice sorgente Go che c'è una miscela in cui a volte le costanti sono tutte maiuscole e altre volte sono camelcase. Hai un riferimento a una specifica?
Jeremy Gailor,

@JeremyGailor Penso che 425nesp stia solo notando che la normale preferenza è che gli sviluppatori li usino come costanti non esportate, quindi usa camelcase. Se lo sviluppatore decide che dovrebbe essere esportato, sentiti libero di usare tutte le maiuscole o maiuscole perché non ci sono preferenze stabilite. Vedi le raccomandazioni per la revisione del codice Golang e la sezione Go efficace su Costanti
waynethec,

C'è una preferenza Proprio come variabili, funzioni, tipi e altri, i nomi delle costanti dovrebbero essere mixedCaps o MixedCaps, non ALLCAPS. Fonte: Go Review Review Comments .
Rodolfo Carvalho,

Si noti che ad es. Le funzioni che prevedono un MessageType accetteranno felicemente i consensi numerici non tipizzati, ad es. 7. Inoltre, è possibile trasmettere qualsiasi int32 a MessageType. Se ne sei consapevole, penso che questo sia il modo più idiomatico in corso.
Kosta

23

È vero che gli esempi precedenti di utilizzo conste iotasono i modi più idiomatici di rappresentare enum primitivi in ​​Go. E se stai cercando un modo per creare un enum più completo simile al tipo che vedresti in un'altra lingua come Java o Python?

Un modo molto semplice per creare un oggetto che inizia ad apparire e sembrare un enumerazione di stringhe in Python sarebbe:

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)
}

Supponiamo di volere anche alcuni metodi di utilità, come Colors.List()e Colors.Parse("red"). E i tuoi colori erano più complessi e dovevano essere una struttura. Quindi potresti fare qualcosa del genere:

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())
}

A quel punto, sicuramente funziona, ma potrebbe non piacerti come devi definire ripetutamente i colori. Se a questo punto desideri eliminarlo, puoi utilizzare i tag sulla tua struttura e fare un po 'di fantasia per impostarlo, ma speriamo che questo sia sufficiente per coprire la maggior parte delle persone.


19

A partire da Go 1.4, lo go generatestrumento è stato introdotto insieme al stringercomando che rende il tuo enum facilmente debuggabile e stampabile.


Sai che è una soluzione opposta. Intendo stringa -> MyType. Poiché la soluzione a senso unico è tutt'altro che ideale. Qui c'è qualcosa che fa quello che voglio, ma scrivere a mano è facile sbagliare.
RS

11

Sono sicuro che abbiamo molte buone risposte qui. Ma ho solo pensato di aggiungere il modo in cui ho usato i tipi enumerati

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())
}

Questo è di gran lunga uno dei modi idiomatici che potremmo creare tipi enumerati e utilizzare in Go.

Modificare:

Aggiunta di un altro modo di utilizzare le costanti per enumerare

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
È possibile dichiarare costanti con valori stringa. IMO è più facile farlo se si intende visualizzarli e in realtà non è necessario il valore numerico.
cbednarski

4

Ecco un esempio che si rivelerà utile quando ci sono molte enumerazioni. Usa le strutture di Golang e attinge ai Principi orientati agli oggetti per legarli tutti insieme in un piccolo fascio ordinato. Nessuno del codice sottostante cambierà quando viene aggiunta o eliminata una nuova enumerazione. Il processo è:

  • Definire una struttura di enumerazione per enumeration items: EnumItem . Ha un numero intero e un tipo di stringa.
  • Definisci enumerationcome un elenco di enumeration items: Enum
  • Costruire metodi per l'enumerazione. Alcuni sono stati inclusi:
    • enum.Name(index int): restituisce il nome per l'indice indicato.
    • enum.Index(name string): restituisce il nome per l'indice indicato.
    • enum.Last(): restituisce l'indice e il nome dell'ultima enumerazione
  • Aggiungi le definizioni di enumerazione.

Ecco un po 'di codice:

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"}}}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.