web-dev-qa-db-fra.com

Quelle est une manière idiomatique de représenter des enums dans Go?

J'essaie de représenter un chromosome simplifié, constitué de N bases, chacune d'entre elles ne pouvant être que l'une des {A, C, T, G}.

J'aimerais formaliser les contraintes avec une énumération, mais je me demande quelle est la manière la plus idiomatique d'imiter une énumération dans Go.

447
carbocation

Citant les spécifications de langue: Iota

Dans une déclaration de constante, l'identifiant prédéfini iota représente des constantes entières non typées successives. Il est réinitialisé à 0 chaque fois que le mot Word réservé apparaît dans la source et s'incrémente après chaque ConstSpec. Il peut être utilisé pour construire un ensemble de constantes liées:

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)

Dans un ExpressionList, la valeur de chaque iota est identique car elle n'est incrémentée qu'après chaque 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
)

Ce dernier exemple exploite la répétition implicite de la dernière liste d'expressions non vides.


Donc, votre code pourrait être comme

const (
        A = iota
        C
        T
        G
)

ou

type Base int

const (
        A Base = iota
        C
        T
        G
)

si vous voulez que les bases soient un type distinct de int.

576
zzzz

En vous référant à la réponse de jnml, vous pouvez empêcher de nouvelles instances de type Base en n’exportant pas du tout le type Base (c’est-à-dire, écrivez-le en minuscule). Si nécessaire, vous pouvez créer une interface exportable avec une méthode qui renvoie un type de base. Cette interface pourrait être utilisée dans des fonctions de l’extérieur qui traitent des bases, c.-à-d.

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
}

À l'intérieur du paquet principal, a.Baser ressemble effectivement à un enum maintenant. Vous ne pouvez définir de nouvelles instances que dans le package.

78
metakeule

À partir de Go 1.4, l'outil go generate a été introduit avec la commande stringer qui rend votre enum facile à déboguer et à imprimer.

17
Moshe Revah

Vous pouvez le faire ainsi:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

Avec ce code, le compilateur devrait vérifier le type d’énumération

16
Azat

Il est vrai que les exemples ci-dessus d'utilisation de const et iota sont les moyens les plus idiomatiques de représenter des énumérations primitives dans Go. Mais que se passe-t-il si vous cherchez un moyen de créer une énumération plus complète similaire à celle que vous verriez dans un autre langage comme Java ou Python?

Un moyen très simple de créer un objet qui commence à ressembler à une énumération de chaîne en Python serait:

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

Supposons que vous souhaitiez également utiliser des méthodes utilitaires, telles que Colors.List() et Colors.Parse("red"). Et vos couleurs étaient plus complexes et devaient être une structure. Ensuite, vous pourriez faire quelque chose un peu comme ceci:

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

À ce stade, cela fonctionne, mais vous n’aimerez peut-être pas la définition répétée des couleurs. Si, à ce stade, vous souhaitez éliminer ce problème, vous pouvez utiliser des balises sur votre structure et effectuer des réflexions sophistiquées pour la configurer, mais espérons que cela suffira à couvrir la plupart des gens.

5
Becca Petrin

Je suis sûr que nous avons beaucoup de bonnes réponses ici. Mais, je viens de penser à ajouter la façon dont j'ai utilisé les types énumérés

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

C’est de loin l’un des moyens idiomatiques de créer des types énumérés et de les utiliser dans Go.

Modifier:

Ajout d'une autre façon d'utiliser des constantes pour énumérer

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)

}
3
wandermonk

Voici un exemple qui s’avérera utile lorsqu’il ya plusieurs énumérations. Il utilise des structures à Golang et s’appuie sur des principes orientés objet pour les lier ensemble dans un joli petit paquet. Aucun du code sous-jacent ne changera lorsqu'une nouvelle énumération est ajoutée ou supprimée. Le processus est:

  • Définissez une structure d'énumération pour enumeration items: EnumItem. Il a un type entier et une chaîne.
  • Définissez la enumeration comme une liste de enumeration items: Enum
  • Construire des méthodes pour l'énumération. Quelques-uns ont été inclus:
    • enum.Name(index int): renvoie le nom de l'index donné.
    • enum.Index(name string): renvoie le nom de l'index donné.
    • enum.Last(): renvoie l'index et le nom de la dernière énumération
  • Ajoutez vos définitions d'énumération.

Voici du 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"}}}
2
Aaron