英文:
What is an idiomatic way of representing enums in Go?
问题
我正在尝试表示一个简化的染色体,它由N个碱基组成,每个碱基只能是{A, C, T, G}
中的一个。
我想用一个枚举来形式化这些约束条件,但我想知道在Go语言中模拟枚举的最常用方式是什么。
英文:
I'm trying to represent a simplified chromosome, which consists of N bases, each of which can only be one of {A, C, T, G}
.
I'd like to formalize the constraints with an enum, but I'm wondering what the most idiomatic way of emulating an enum is in Go.
答案1
得分: 885
引用自语言规范:Iota
> 在常量声明中,预声明的标识符iota表示连续的无类型整数常量。每当源代码中出现保留字const时,它将被重置为0,并在每个ConstSpec之后递增。它可以用于构建一组相关的常量:
const ( // iota被重置为0
c0 = iota // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
const (
a = 1 << iota // a == 1(iota已被重置)
b = 1 << iota // b == 2
c = 1 << iota // c == 4
)
const (
u = iota * 42 // u == 0 (无类型整数常量)
v float64 = iota * 42 // v == 42.0 (float64常量)
w = iota * 42 // w == 84 (无类型整数常量)
)
const x = iota // x == 0(iota已被重置)
const y = iota // y == 0(iota已被重置)
> 在ExpressionList中,每个iota的值相同,因为它仅在每个ConstSpec之后递增:
const (
bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0
bit1, mask1 // bit1 == 2, mask1 == 1
_, _ // 跳过iota == 2
bit3, mask3 // bit3 == 8, mask3 == 7
)
> 最后一个示例利用了最后一个非空表达式列表的隐式重复。
因此,你的代码可以是这样的:
const (
A = iota
C
T
G
)
或者
type Base int
const (
A Base = iota
C
T
G
)
如果你希望bases是一个与int不同的类型。
英文:
Quoting from the language specs:Iota
> Within a constant declaration, the predeclared identifier iota represents successive untyped integer constants. It is reset to 0 whenever the reserved word const appears in the source and increments after each ConstSpec. It can be used to construct a set of related constants:
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)
> Within an ExpressionList, the value of each iota is the same because it is only incremented after each 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
)
> This last example exploits the implicit repetition of the last non-empty expression list.
So your code might be like
const (
A = iota
C
T
G
)
or
type Base int
const (
A Base = iota
C
T
G
)
if you want bases to be a separate type from int.
答案2
得分: 117
参考jnml的答案,您可以通过根本不导出Base类型(即将其写为小写)来防止创建Base类型的新实例。如果需要,您可以创建一个可导出的接口,该接口具有返回基本类型的方法。这个接口可以在外部处理Bases的函数中使用,例如:
package a
type base int
const (
A base = iota
C
T
G
)
type Baser interface {
Base() base
}
// 每个base都必须满足Baser接口
func(b base) Base() base {
return b
}
func(b base) OtherMethod() {
}
package main
import "a"
// 外部函数通过a.Baser处理a.base
// 由于a.base没有被导出,因此只能使用在a包内创建的导出的bases,如a.A、a.C、a.T和a.G
func HandleBasers(b a.Baser) {
base := b.Base()
base.OtherMethod()
}
// 外部函数根据条件返回a.A或a.C
func AorC(condition bool) a.Baser {
if condition {
return a.A
}
return a.C
}
在main包中,a.Baser
现在实际上就像一个枚举类型。只有在a包内部才能定义新的实例。
英文:
Referring to the answer of jnml, you could prevent new instances of Base type by not exporting the Base type at all (i.e. write it lowercase). If needed, you may make an exportable interface that has a method that returns a base type. This interface could be used in functions from the outside that deal with Bases, i.e.
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
}
Inside the main package a.Baser
is effectively like an enum now.
Only inside the a package you may define new instances.
答案3
得分: 64
你可以这样做:
type MessageType int32
const (
TEXT MessageType = 0
BINARY MessageType = 1
)
使用这段代码,编译器应该检查枚举的类型。
英文:
You can make it so:
type MessageType int32
const (
TEXT MessageType = 0
BINARY MessageType = 1
)
With this code compiler should check type of enum
答案4
得分: 61
这是使用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())
}
到这一点,确实可以工作,但你可能不喜欢如何重复定义颜色。如果你想要消除这一点,你可以在结构体上使用标签,并使用一些反射技巧来设置它,但希望这足以满足大多数人的需求。
英文:
It's true that the above examples of using const
and iota
are the most idiomatic ways of representing primitive enums in Go. But what if you're looking for a way to create a more fully-featured enum similar to the type you'd see in another language like Java or Python?
A very simple way to create an object that starts to look and feel like a string enum in Python would be:
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)
}
Suppose you also wanted some utility methods, like Colors.List()
, and Colors.Parse("red")
. And your colors were more complex and needed to be a struct. Then you might do something a bit like this:
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())
}
At that point, sure it works, but you might not like how you have to repetitively define colors. If at this point you'd like to eliminate that, you could use tags on your struct and do some fancy reflecting to set it up, but hopefully this is enough to cover most people.
答案5
得分: 41
有一种方法是使用结构体命名空间。
好处是所有的枚举变量都在一个特定的命名空间下,以避免污染。
问题是我们只能使用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",
}
英文:
There is a way with struct namespace.
The benefit is all enum variables are under a specific namespace to avoid pollution.
The issue is that we could only use var
not 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",
}
答案6
得分: 22
从Go 1.4版本开始,引入了go generate
工具,以及与之一起使用的stringer
命令,使得您的枚举类型易于调试和打印。
英文:
As of Go 1.4, the go generate
tool has been introduced together with the stringer
command that makes your enum easily debuggable and printable.
答案7
得分: 20
对于这样的用例,使用字符串常量可能很有用,因为它可以被编组成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
英文:
For a use case like this, it may be useful to use a string constant so it can be marshaled into a JSON string. In the following example, []Base{A,C,G,T}
would get marshaled to ["adenine","cytosine","guanine","thymine"]
.
type Base string
const (
A Base = "adenine"
C = "cytosine"
G = "guanine"
T = "thymine"
)
When using iota
, the values get marshaled into integers. In the following example, []Base{A,C,G,T}
would get marshaled to [0,1,2,3]
.
type Base int
const (
A Base = iota
C
G
T
)
Here's an example comparing both approaches:
答案8
得分: 19
我确定我们在这里有很多好答案。但是,我刚想到了添加我使用过的枚举类型的方法。
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不记录任何内容
UNSPECIFIED Level = iota // 0 :
// TRACE记录所有内容
TRACE // 1
// INFO记录信息、警告和错误
INFO // 2
// WARNING记录警告和错误
WARNING // 3
// ERROR只记录错误
ERROR // 4
)
// 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)
}
英文:
I am sure we have a lot of good answers here. But, I just thought of adding the way I have used enumerated types
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())
}
This is by far one of the idiomatic ways we could create Enumerated types and use in Go.
Edit:
Adding another way of using constants to enumerate
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)
}
答案9
得分: 9
这是一个例子,当有许多枚举时,它将证明有用。它使用了Golang中的结构,并借鉴了面向对象的原则,将它们整合在一个整洁的包中。当添加或删除新的枚举时,底层代码不会改变。过程如下:
- 为
枚举项
定义一个枚举结构:EnumItem。它有一个整数和字符串类型。 - 将
枚举
定义为枚举项
的列表: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"
}
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"}}}
英文:
Here is an example that will prove useful when there are many enumerations. It uses structures in Golang, and draws upon Object Oriented Principles to tie them all together in a neat little bundle. None of the underlying code will change when a new enumeration is added or deleted. The process is:
- Define an enumeration structure for
enumeration items
: EnumItem. It has an integer and string type. - Define the
enumeration
as a list ofenumeration items
: Enum - Build methods for the enumeration. A few have been included:
enum.Name(index int)
: returns the name for the given index.enum.Index(name string)
: returns the name for the given index.enum.Last()
: returns the index and name of the last enumeration
- Add your enumeration definitions.
Here is some code:
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"}}}
答案10
得分: 6
重构了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 { // 每个EFoo都必须满足IEFoo接口
return e
}
func(e EFoo) otherMethod() { // "private"
//一些逻辑
}
英文:
Refactored https://stackoverflow.com/a/17989915/863651 to make it a bit more readable:
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
得分: 4
这是在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]
英文:
This is a safe way to implement enum in 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)
}
The output:
Colors = [RED GREEN BLUE]
Genders = [1 2]
答案12
得分: 4
此外,这是一种在一个字节中存储不同角色的相当有效的方法,其中第一个值设置为1,通过iota进行位移。
package main
import "fmt"
const (
isCaptain = 1 << iota
isTrooper
isMedic
canFlyMars
canFlyJupiter
canFlyMoon
)
func main() {
var roles byte = isCaptain | isMedic | canFlyJupiter
//打印二进制表示。
fmt.Printf("%b\n", roles)
fmt.Printf("%b\n", isCaptain)
fmt.Printf("%b\n", isTrooper)
fmt.Printf("%b\n", isMedic)
fmt.Printf("是船长吗?%v\n", isCaptain&roles == isCaptain)
fmt.Printf("是士兵吗?%v", isTrooper&roles == isTrooper)
}
英文:
Also, this is a pretty effective way to store different roles in one location in a byte, where the first value is set to 1, bit shifted by an 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)
}
答案13
得分: 4
我这样创建了枚举。假设我们需要一个表示性别的枚举。可能的值有男性、女性、其他。
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
}
英文:
I created the enum this way. Suppose we need an enum representing gender. Possible values are Male, Female, Others
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
}
答案14
得分: 1
一个我发现的更简单的工作方式。
const (
Stake TX = iota
Withdraw)
type TX int
func (t TX) String() string {
return [...]string{"STAKE", "WITHDRAW"}[t]}
log.Println(Stake.String()) --> STAKE
英文:
A simpler way I have found to work.
const (
Stake TX = iota
Withdraw)
type TX int
func (t TX) String() string {
return [...]string{"STAKE", "WITHDRAW"}[t]}
log.Println(Stake.String()) --> STAKE
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论