【问题标题】:What is an idiomatic way of representing enums in Go?在 Go 中表示枚举的惯用方式是什么?
【发布时间】:2013-01-03 18:47:54
【问题描述】:

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

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

【问题讨论】:

标签: enums go genetics


【解决方案1】:
推荐的答案 Go Language

引用语言规范: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 不同的类型。

【讨论】:

  • 很好的例子(我不记得确切的 iota 行为——当它增加时——来自规范)。就我个人而言,我喜欢给枚举赋予一个类型,因此当用作参数、字段等时可以对其进行类型检查。
  • 非常有趣的@jnml。但我有点失望,静态类型检查似乎很松散,例如没有什么能阻止我使用从未存在的 Base n°42:play.golang.org/p/oH7eiXBxhR
  • 作为对 jnml 的补充,即使在语义上,语言中也没有说定义为 Base 的 const 代表了有效 Base 的整个范围,它只是说这些特定的 const 是 Base 类型。更多的常量也可以在其他地方定义为 Base,它甚至不是互斥的(例如 const Z Base = 0 可以定义并且是有效的)。
  • 您可以使用iota + 1 不从0开始。
  • 请注意,在上一个示例中,C、T、G 是无类型的数字常量,而不是 Base 类型。要使所有 const 成为 Base,您需要在每一行上重复 Base。
【解决方案2】:

参考 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()  {
}

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

【讨论】:

  • 您的方法似乎非常适合 base 仅用作方法接收器的情况。如果您的a 包要公开一个采用base 类型参数的函数,那么它会变得很危险。实际上,用户可以只使用字面值 42 调用它,函数将接受 base,因为它可以转换为 int。为防止这种情况,请将 base 设为 struct: type base struct{value:int}。问题:您不能再将基数声明为常量,只能声明模块变量。但是 42 永远不会被转换为该类型的 base
  • @metakeule 我正在尝试理解您的示例,但您对变量名的选择使其变得异常困难。
  • 这是我在示例中遇到的问题之一。 FGS,我意识到这很诱人,但不要将变量命名为与类型相同!
【解决方案3】:

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

【讨论】:

  • 你知道是相反的解决方案。我的意思是字符串-> MyType。由于一种方式的解决方案远非理想。 Here 是做我想做的事情的 sb gist - 但是手写很容易出错。
【解决方案4】:

你可以这样做:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

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

【讨论】:

  • 常量通常用正常的驼峰写,而不是全部大写。首字母大写表示该变量已导出,可能是也可能不是您想要的。
  • 我注意到在 Go 源代码中有一个混合体,有时常量都是大写的,有时它们是驼峰式的。你有规范的参考吗?
  • @JeremyGailor 我认为 425nesp 只是注意到开发人员通常偏好将它们用作 unexported 常量,因此请使用驼峰式。如果开发人员确定应该将其导出,则可以随意使用全部大写或大写,因为没有确定的偏好。见Golang Code Review RecommendationsEffective Go Section on Constants
  • 有一个偏好。就像变量、函数、类型等一样,常量名应该是混合大写或混合大写,而不是全大写。来源:Go Code Review Comments.
  • 请注意,例如期望 MessageType 的函数将很乐意接受无类型的数字常量,例如7. 此外,您可以将任何 int32 转换为 MessageType。如果您意识到这一点,我认为这是最惯用的方式。
【解决方案5】:

确实,上面使用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())
}

到那时,确定它可以工作,但您可能不喜欢重复定义颜色的方式。如果此时你想消除它,你可以在你的结构上使用标签并做一些花哨的反射来设置它,但希望这足以覆盖大多数人。

【讨论】:

  • 有什么方法可以将它与 switch 语句一起使用,比如在 Java 中?
  • 实际上,我在您的示例中使用公共Colors 使其工作,例如case Colors.Red: ...
【解决方案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)

}

【讨论】:

  • 您可以用字符串值声明常量。如果您打算显示它们并且实际上不需要数值,IMO 会更容易做到这一点。
【解决方案7】:

这里有一个例子,当有很多枚举时它会被证明是有用的。它使用 Golang 中的结构,并利用面向对象的原则将它们捆绑在一个整洁的小包中。添加或删除新枚举时,任何底层代码都不会更改。流程是:

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

【讨论】:

    【解决方案8】:

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

    好处是所有枚举变量都在特定的命名空间下以避免污染。 问题是我们只能使用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",
    }
    

    【讨论】:

    • 这在语法上很好,但我对人们更改常量感到偏执。请参阅此示例:play.golang.org/p/9D1tMQJVmIc。如果命名空间很重要,我倾向于将它们放在自己的包中。
    • 如何将其用作枚举?
    • 我同意@Grokify,虽然这在语法上看起来很讨人喜欢,但在这里使用 var 是非常危险的,因为它很容易改变,这违背了枚举的全部意义。
    【解决方案9】:

    对于这样的用例,使用字符串常量可能很有用,以便可以将其编组为 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

    【讨论】:

      【解决方案10】:

      重构 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
      }
      

      【讨论】:

        【解决方案11】:

        这是在 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]
        

        【讨论】:

          【解决方案12】:

          此外,这是将不同角色存储在一个字节中的一个位置的一种非常有效的方法,其中第一个值设置为 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)
          
          }
          

          【讨论】:

            猜你喜欢
            • 2019-03-27
            • 1970-01-01
            • 1970-01-01
            • 2018-02-04
            • 2019-09-27
            • 2012-11-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多