Что такое идиоматический способ представления перечислений в Go?
Я пытаюсь представить упрощенную хромосому, состоящую из N баз, каждая из которых может быть только одной из {A, C, T, G}
.
Я хотел бы формализовать ограничения с перечислением, но мне интересно, какой самый идиоматический способ эмуляции перечисления в Go.
Ответы
Ответ 1
Цитата из спецификации языка: Iota
В объявлении константы идентификатор iota с предустановленным идентификатором представляет собой последовательные нетипизированные целочисленные константы. Это значение reset равно 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 значение каждой iota одинаково, потому что оно только увеличивается после каждого 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.
Ответ 2
Ссылаясь на ответ 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
по сути как enum. Только внутри пакета вы можете определить новые экземпляры.
Ответ 3
Начиная с Go 1.4, инструмент go generate
был введен вместе с командой stringer
, которая делает ваше перечисление легко отлаживаемым и пригодным для печати.
Ответ 4
Вы можете сделать это так:
type MessageType int32
const (
TEXT MessageType = 0
BINARY MessageType = 1
)
С помощью этого кода компилятор должен проверить тип перечисления
Ответ 5
Это правда, что приведенные выше примеры использования 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())
}
На этом этапе, конечно, это работает, но вам может не понравиться то, как вы должны повторно определять цвета. Если в этот момент вы захотите устранить это, вы можете использовать теги в своей структуре и поразмышлять над ее настройкой, но, надеюсь, этого достаточно, чтобы охватить большинство людей.
Ответ 6
Уверен, у нас здесь много хороших ответов. Но я просто подумал добавить способ, которым я использовал перечисляемые типы
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)
}
Ответ 7
Вот пример, который окажется полезным, когда есть много перечислений. Он использует структуры в Голанге и опирается на объектно-ориентированные принципы, чтобы связать их вместе в аккуратный маленький пучок. Ни один из базового кода не изменится при добавлении или удалении нового перечисления. Процесс такой:
- Определите структуру перечисления для
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"}}}