在Go中表示枚举的惯用方法是什么?

我试图表示一个简化的染色体,它由N个碱基组成,每个碱基只能是{A, C, T, G}之一。

我想用枚举形式化约束,但我想知道在Go中模拟枚举的最惯用的方法是什么。

484987 次浏览

引用语言规范:Iota

在常量声明中,预先声明的标识符iota表示连续的无类型整数常量。每当保留字const出现在源中并在每个ConstSpec之后递增时,它就会重置为0。它可用于构造一组相关的常量:

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分开的类型。

参考jnml的答案,您可以通过根本不导出Base类型来防止新的Base类型实例(即写小写)。如果需要,您可以制作一个具有返回基类型的方法的可导出接口。此接口可用于处理Base的外部函数,即。

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现在实际上就像一个枚举。 只有在包中,你才可以定义新的实例。

从Go 1.4开始,go generate工具与stringer命令一起引入,使您的枚举易于调试和打印。

你可以这样做:

type MessageType int32


const (
TEXT   MessageType = 0
BINARY MessageType = 1
)

使用此代码编译器应该检查枚举的类型

确实,上面使用constiota的示例是在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())
}

在这一点上,当然它是有效的,但是你可能不喜欢你必须重复定义颜色的方式。如果在这一点上你想消除它,你可以在你的结构上使用标签并做一些花哨的反射来设置它,但希望这足以覆盖大多数人。

我相信我们在这里有很多很好的答案。但是,我只是想添加我使用枚举类型的方式

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 itemsEnumItem定义一个枚举结构。它有整数和字符串类型。
  • enumeration定义为enumeration itemsenum的列表
  • 枚举的构建方法。包括一些:
    • 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"}}}

有一种使用struct命名空间的方法。

好处是所有枚举变量都在特定的命名空间下,以避免污染。 问题是我们只能使用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字符串中。在以下示例中,[]Base{A,C,G,T}将封送到["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

重构https://stackoverflow.com/a/17989915/863651使其更具可读性:

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
}

这是在Go语言中实现枚举的安全方法:

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,位移动了iota。

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)


}

我以这种方式创建了枚举。假设我们需要一个代表性别的枚举。可能的值是男性、女性、其他

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
}

我找到了一种更简单的工作方式。

const (
Stake TX = iota
Withdraw)




type TX int


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


log.Println(Stake.String()) --> STAKE