问题
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:
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:
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:
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:
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:
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.
回答6:
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)
}
回答7:
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"}}}
回答8:
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",
}
回答9:
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:
https://play.golang.org/p/VvkcWvv-Tvj
来源:https://stackoverflow.com/questions/65497917/rewriting-node-enum-model-in-go