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

car*_*ion 478 enums go genetics

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

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

zzz*_*zzz 621

引用语言规范: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)
Run Code Online (Sandbox Code Playgroud)

在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
)
Run Code Online (Sandbox Code Playgroud)

最后一个示例利用最后一个非空表达式列表的隐式重复.


所以你的代码可能就像

const (
        A = iota
        C
        T
        G
)
Run Code Online (Sandbox Code Playgroud)

要么

type Base int

const (
        A Base = iota
        C
        T
        G
)
Run Code Online (Sandbox Code Playgroud)

如果你想base和int是一个单独的类型.

  • 很好的例子(我没有回忆起确切的iota行为 - 当它增加时 - 来自规范).我个人喜欢给枚举一个类型,所以当它用作参数,字段等时可以进行类型检查. (15认同)
  • 非常有趣@jnml.但我有点失望的是静态类型检查似乎是松散的,例如没有什么能阻止我使用从未存在的Base n°42:http://play.golang.org/p/oH7eiXBxhR (14认同)
  • 你可以使用```iota + 1```来从0开始. (9认同)
  • 为了补充jnml,即使在语义上,语言中没有任何内容表示定义为Base的consts代表整个有效Base的范围,它只是说这些特定的consts是Base类型.更多的常量也可以在别处定义为Base,并且它甚至不是互斥的(例如,const Z Base = 0可以被定义并且是有效的). (6认同)
  • Go没有数字子范围类型的概念,例如Pascal的,所以`Ord(Base)`不仅限于`0..3`,而且与其基础数字类型具有相同的限制.这是语言设计的选择,安全性和性能之间的妥协.每次触摸"Base"类型值时,请考虑"安全"运行时绑定检查.或者如何为算术和'++'和` - `定义'Base`值的'溢出'行为?等等. (4认同)
  • 请注意,在上一个示例中,C、T、G 是无类型数字常量,而不是 Base 类型。为了使所有常量都是 Base,您需要在每一行上重复 Base。 (3认同)
  • @Kosta 在当前版本中使用“A base = iota”,所有值都正确具有“Base”类型。如果它们是无类型的数字常量,我们可以将它们用作整数,但我们不能 https://play.golang.org/p/Dpop4S3qdNs (3认同)
  • @zzzz:如果可以定义一个只允许比较的类型,一组固定的提供值和对该集合的迭代(即没有算术运算或转换),这将涵盖枚举的常见用法.所有这些都可以在编译时检查并以简单的形式实现,因此它具有安全性和性能. (2认同)
  • iota真的是有益的构造吗?Iota 大多数时候提供每行 2 个字符(好的 + 空格)的节省,它的成本是:枚举值取决于顺序 - 一旦它在源中发生更改(例如通过 CVS 合并),您可能会损坏现有的持久数据通过移动索引,导致数据含义的改变。其次,如果您在枚举中有大约 15 个项目,并且只想快速检查,哪个项目对应于值 11,您是否要每次都计算行数?说真的,我们写一次,读几十遍。 (2认同)

met*_*ule 82

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

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()  {
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)

主包内部a.Baser现在实际上就像一个枚举.只有在包中,您才可以定义新实例.

  • 对于`base`仅用作方法接收器的情况,您的方法似乎是完美的.如果你的`a`包暴露了一个带有`base`类型参数的函数,那么它就会变得危险.实际上,用户可以用文字值42来调用它,该函数将接受为"base",因为它可以被转换为int.为了防止这种情况,使`base`成为`struct`:`type base struct {value:int}`.问题:你不能再将base声明为常量,只能声明模块变量.但是42永远不会被强加给那种类型的"基地". (9认同)
  • @metakeule我试图理解你的例子,但你在变量名中的选择使得它非常困难. (5认同)
  • 这是我在示例中遇到的问题之一。FGS,我意识到这很诱人,但不要将变量命名为与类型相同的名称! (2认同)

小智 22

你可以这样做:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)
Run Code Online (Sandbox Code Playgroud)

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

  • 常量通常用普通的camelcase编写,而不是全部大写.初始大写字母表示导出变量,可能是您想要的也可能不是. (4认同)
  • 我注意到在 Go 源代码中存在一种混合情况,有时常量都是大写,有时是驼峰式。您有规范参考吗? (4认同)
  • 请注意,例如,需要 MessageType 的函数会很乐意接受无类型的数字常量,例如 7。此外,您可以将任何 int32 转换为 MessageType。如果你意识到这一点,我认为这是 go 中最惯用的方式。 (2认同)

Yu *_*ang 20

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

好处是所有枚举变量都在特定的命名空间下以避免污染。问题是我们只能使用varnotconst

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",
}
Run Code Online (Sandbox Code Playgroud)


Mos*_*vah 17

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


Bec*_*rin 12

确实,上面的使用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)
}
Run Code Online (Sandbox Code Playgroud)

假设你也想要一些实用方法,比如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())
}
Run Code Online (Sandbox Code Playgroud)

在这一点上,确定它有效,但你可能不喜欢你必须重复定义颜色.如果此时你想消除它,你可以在你的结构上使用标签并做一些反思来设置它,但希望这足以覆盖大多数人.


Gro*_*ify 10

对于这样的用例,使用字符串常量可能很有用,因此可以将其编组为 JSON 字符串。在以下示例中,[]Base{A,C,G,T}将被编组为["adenine","cytosine","guanine","thymine"].

type Base string

const (
    A Base = "adenine"
    C      = "cytosine"
    G      = "guanine"
    T      = "thymine"
)
Run Code Online (Sandbox Code Playgroud)

使用时iota,这些值被编组为整数。在以下示例中,[]Base{A,C,G,T}将被编组为[0,1,2,3].

type Base int

const (
    A Base = iota
    C
    G
    T
)
Run Code Online (Sandbox Code Playgroud)

这是比较两种方法的示例:

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


wan*_*onk 7

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

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())
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,这是我们可以创建枚举类型并在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)

}
Run Code Online (Sandbox Code Playgroud)

  • 您可以使用字符串值声明常量。如果您打算显示它们并且实际上不需要数字值,则IMO会更容易做到这一点。 (2认同)

小智 7

下面是一个例子,当有很多枚举时,它会被证明是有用的。它使用 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"}}}
Run Code Online (Sandbox Code Playgroud)


XDS*_*XDS 6

重构/sf/answers/1259294081/以使其更具可读性:

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
}
Run Code Online (Sandbox Code Playgroud)


kau*_*hik 5

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

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
}
Run Code Online (Sandbox Code Playgroud)