Что такое идиоматический способ представления перечислений в 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
)
Вот пример сравнения обоих подходов:
Начиная с версии 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