Czym jest idiomatyczny sposób przedstawiania enum w Go?

Próbuję przedstawić uproszczony chromosom, który składa się z N baz, z których każda może być tylko jedną z {A, C, T, G}.

Chciałbym sformalizować ograniczenia za pomocą enum, ale zastanawiam się, jaki jest najbardziej idiomatyczny sposób emulowania enum w Go.

Author: Philip Kirkbride, 2013-01-20

6 answers

Cytowanie ze specyfikacji języka: Iota

W deklaracji stałej, poprzedzający identyfikator iota reprezentuje kolejne nieprzepisane stałe całkowite. Jest resetowany do 0, gdy słowo kluczowe const pojawi się w źródle i zwiększa się po każdym ConstSpec. Można go użyć do skonstruowania zbioru powiązanych stałych:

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)

W obrębie listy wyrażeń wartość każdej ioty jest taka sama, ponieważ jest zwiększana tylko po każdej 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
)

Ten ostatni przykład wykorzystuje Ukryte powtórzenie ostatniej niepustej listy wyrażeń.


Więc Twój kod może być jak

const (
        A = iota
        C
        T
        G
)

Lub

type Base int

const (
        A Base = iota
        C
        T
        G
)

Jeśli chcesz, aby bazy były oddzielnym typem od int.

 509
Author: zzzz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-20 17:07:34

Odwołując się do odpowiedzi jnml, możesz zapobiec nowym instancjom typu bazowego, nie eksportując w ogóle typu bazowego (tzn. pisząc go małymi literami). W razie potrzeby można utworzyć interfejs eksportowalny, który ma metodę zwracającą typ bazowy, tak aby ten interfejs mógł być używany w funkcjach z zewnątrz, które zajmują się bazami, np.

package a

type base int

const (
    A base = iota
    C
    T
    G
)


type Baser interface {
    Base() base
}

// every base must fullfill 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
}

Wewnątrz głównego pakietu a.Baser jest teraz jak enum. Tylko wewnątrz pakietu a można zdefiniować nowe instancje.

 63
Author: metakeule,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-02 07:09:22

Od wersji Go 1.4 narzędzie go generate zostało wprowadzone wraz z stringer polecenie, które sprawia, że Twoje wyliczenie jest łatwe do debugowania i drukowania.

 15
Author: Moshe Revah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-05 12:01:29

You can make it so:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

Za pomocą tego kompilatora kodu należy sprawdzić typ enum

 10
Author: Azat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-21 11:05:08

To prawda, że powyższe przykłady użycia const i iota są najbardziej idiomatycznymi sposobami przedstawiania prymitywnych enum w Go. Ale co, jeśli szukasz sposobu na stworzenie bardziej funkcjonalnego enum podobnego do typu, który można zobaczyć w innym języku, takim jak Java lub Python?

Bardzo prostym sposobem na stworzenie obiektu, który zaczyna wyglądać i czuć się jak string enum w Pythonie jest:

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

Załóżmy, że potrzebujesz również metod użytkowych, takich jak Colors.List() i Colors.Parse("red"). I twoje kolory były bardziej złożone i musiały być strukturą. Wtedy możesz zrobić coś takiego:

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

W tym momencie, na pewno to działa, ale może Ci się nie spodobać, jak musisz powtarzalnie definiować kolory. Jeśli w tym momencie chcesz to wyeliminować, możesz użyć tagów na swojej strukturze i zrobić fantazyjne refleksje, aby ją skonfigurować, ale mam nadzieję, że to wystarczy, aby pokryć większość ludzi.

 4
Author: Becca Petrin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-19 22:54:01

Jestem pewien, że mamy tu wiele dobrych odpowiedzi. Ale po prostu pomyślałem o dodaniu sposobu, w jaki użyłem typów wyliczeniowych

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

Jest to zdecydowanie jeden z idiomatycznych sposobów, w jaki możemy tworzyć typy wyliczeniowe i używać ich w Go.

Edit:

Dodanie innego sposobu użycia stałych do wyliczenia

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
Author: wandermonk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-30 08:10:14