Что такое идиоматический способ представления перечислений в Go?

Я пытаюсь представить упрощенную хромосому, которая состоит из N оснований, каждое из которых может быть только одним из {A, C, T, G},

Я хотел бы формализовать ограничения с помощью enum, но мне интересно, какой самый идиоматичный способ эмуляции enum есть в Go.

14 ответов

Решение

Цитирование из спецификации языка: Йота

В объявлении константы предварительно объявленный идентификатор iota представляет последовательные нетипизированные целочисленные константы. Он сбрасывается в 0 всякий раз, когда зарезервированное слово const появляется в источнике и увеличивается после каждого ConstSpec. Его можно использовать для построения набора связанных констант:

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)

Внутри ExpressionList значение каждой йоты одинаково, поскольку оно увеличивается только после каждого 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
)

В этом последнем примере используется неявное повторение последнего непустого списка выражений.


Таким образом, ваш код может быть как

const (
        A = iota
        C
        T
        G
)

или же

type Base int

const (
        A Base = iota
        C
        T
        G
)

если вы хотите, чтобы основания были отдельным типом от int.

Обращаясь к ответу jnml, вы можете предотвратить появление новых экземпляров базового типа, вообще не экспортируя базовый тип (то есть писать его строчными буквами). При необходимости вы можете создать экспортируемый интерфейс с методом, который возвращает базовый тип. Этот интерфейс может использоваться в функциях извне, которые имеют дело с основами, т.е.

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
}

Внутри основного пакета a.Baser эффективно, как перечисление сейчас. Только внутри пакета вы можете определить новые экземпляры.

Вы можете сделать это так:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

С этим кодом компилятор должен проверять тип перечисления

Это правда, что приведенные выше примеры использования const а также iota являются наиболее идиоматическими способами представления примитивных перечислений в Go. Но что, если вы ищете способ создания более полнофункционального перечисления, подобного типу, который вы видели бы на другом языке, таком как Java или Python?

Очень простой способ создать объект, который начинает выглядеть и чувствовать себя как строковое перечисление в Python:

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

Предположим, вы также хотели некоторые служебные методы, такие как Colors.List(), а также Colors.Parse("red"), И ваши цвета были более сложными и должны были быть структурой. Тогда вы можете сделать что-то вроде этого:

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

На этом этапе, конечно, это работает, но вам может не понравиться то, как вы должны периодически определять цвета. Если на этом этапе вы захотите устранить это, вы можете использовать теги в своей структуре и поразмышлять над ее настройкой, но, надеюсь, этого достаточно, чтобы охватить большинство людей.

Есть способ с пространством имен структуры.

Преимущество заключается в том, что все переменные перечисления находятся в определенном пространстве имен, чтобы избежать загрязнения. Проблема в том, что мы могли использовать только var не const

type OrderStatusType string

var OrderStatus = struct {
    APPROVED         OrderStatusType
    APPROVAL_PENDING OrderStatusType
    REJECTED         OrderStatusType
    REVISION_PENDING OrderStatusType
}{
    APPROVED:         "approved",
    APPROVAL_PENDING: "approval pending",
    REJECTED:         "rejected",
    REVISION_PENDING: "revision pending",
}

Для такого случая использования может быть полезно использовать строковую константу, чтобы ее можно было маршалировать в строку JSON. В следующем примере маршалируется на ["adenine","cytosine","guanine","thymine"].

      type Base string

const (
    A Base = "adenine"
    C      = "cytosine"
    G      = "guanine"
    T      = "thymine"
)

Когда используешь iota, значения упорядочиваются в целые числа. В следующем примере []Base{A,C,G,T} будет направлен на [0,1,2,3].

      type Base int

const (
    A Base = iota
    C
    G
    T
)

Вот пример сравнения обоих подходов:

https://play.golang.org/p/VvkcWvv-Tvj

Начиная с версии 1.4, go generate инструмент был представлен вместе с stringer Команда, которая делает ваше перечисление легко отлаживаемым и печатным.

Уверен, у нас здесь много хороших ответов. Но я просто подумал добавить способ, которым я использовал перечисляемые типы

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

Это, безусловно, один из идиоматических способов, которыми мы могли бы создавать перечисляемые типы и использовать их в Go.

Редактировать:

Добавление другого способа использования констант для перечисления

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)

}

Вот пример, который будет полезен при большом количестве перечислений. Он использует структуры в Golang и опирается на объектно-ориентированные принципы, чтобы связать их все вместе в аккуратный небольшой пучок. Ни один из базовых кодов не изменится при добавлении или удалении нового перечисления. Процесс такой:

  • Определите структуру перечисления для enumeration items: EnumItem. Имеет целочисленный и строковый тип.
  • Определить enumeration как список enumeration items: Enum
  • Методы сборки для перечисления. Некоторые из них были включены:
    • enum.Name(index int): возвращает имя для данного индекса.
    • enum.Index(name string): возвращает имя для данного индекса.
    • enum.Last(): возвращает индекс и имя последнего перечисления
  • Добавьте свои определения перечисления.

Вот код:

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

Рефакторинг /questions/45752343/chto-takoe-idiomaticheskij-sposob-predstavleniya-perechislenij-v-go/45752356#45752356 чтобы сделать его более читаемым:

      package SampleEnum

type EFoo int

const (
    A EFoo = iota
    C
    T
    G
)

type IEFoo interface {
    Get() EFoo
}

func(e EFoo) Get() EFoo { // every EFoo must fulfill the IEFoo interface
    return e
}

func(e EFoo) OtherMethod()  { // "private"
    //some logic
}

Я создал перечисление таким образом. Предположим, нам нужно перечисление, представляющее пол. Возможные значения: «Мужской», «Женский», «Другие».

      package gender

import (
    "fmt"
    "strings"
)

type Gender struct {
    g string
}

var (
    Unknown = Gender{}
    Male    = Gender{g: "male"}
    Female  = Gender{g: "female"}
    Other   = Gender{g: "other"}
)

var genders = []Gender{
    Unknown,
    Male,
    Female,
    Other,
}

func Parse(code string) (parsed Gender, err error) {
    for _, g := range genders {
        if g.g == strings.ToLower(code) {
            if g == Unknown {
                err = fmt.Errorf("unknown gender")
            }
            parsed = g
            return
        }
    }

    parsed = Unknown
    err = fmt.Errorf("unknown gender", code)
    return
}

func (g Gender) Gender() string {
    return g.g
}

Это безопасный способ реализовать enum в golang:

      package main

import (
    "fmt"
)

const (
    MALE   = _gender(1)
    FEMALE = _gender(2)
    RED    = _color("RED")
    GREEN  = _color("GREEN")
    BLUE   = _color("BLUE")
)

type Gender interface {
    _isGender()
    Value() int
}

type _gender int

func (_gender) _isGender() {}

func (_g _gender) Value() int {
    return int(_g)
}

type Color interface {
    _isColor()
    Value() string
}

type _color string

func (_color) _isColor() {}

func (_c _color) Value() string {
    return string(_c)
}

func main() {
    genders := []Gender{MALE, FEMALE}
    colors := []Color{RED, GREEN, BLUE}
    fmt.Println("Colors =", colors)
    fmt.Println("Genders =", genders)
}

Выход:

      Colors = [RED GREEN BLUE]
Genders = [1 2]

Кроме того, это довольно эффективный способ хранить разные роли в одном месте в байте, где первое значение установлено в 1, сдвинутое на йоту.

      package main

import "fmt"

const (
    isCaptain = 1 << iota
    isTrooper
    isMedic

    canFlyMars
    canFlyJupiter
    canFlyMoon
)

func main() {
    var roles byte = isCaptain | isMedic | canFlyJupiter
    //Prints a binary representation.
    fmt.Printf("%b\n", roles)
    fmt.Printf("%b\n", isCaptain)
    fmt.Printf("%b\n", isTrooper)
    fmt.Printf("%b\n", isMedic)

    fmt.Printf("Is Captain? %v\n", isCaptain&roles == isCaptain)
    fmt.Printf("Is Trooper? %v", isTrooper&roles == isTrooper)

}

Более простой способ я нашел для работы.

      const (
Stake TX = iota
Withdraw)


type TX int

func (t TX) String() string {
return [...]string{"STAKE", "WITHDRAW"}[t]}

log.Println(Stake.String()) --> STAKE
Другие вопросы по тегам