Một cách thành ngữ để đại diện cho enum trong Go là gì?


522

Tôi đang cố gắng đại diện cho một nhiễm sắc thể đơn giản hóa, bao gồm N cơ sở, mỗi cơ sở chỉ có thể là một trong số đó {A, C, T, G}.

Tôi muốn chính thức hóa các ràng buộc với một enum, nhưng tôi tự hỏi cách mô phỏng thành ngữ nhất của enum là gì trong Go.


4
Trong các gói tiêu chuẩn, chúng được biểu diễn dưới dạng hằng số. Xem golang.org/pkg/os/#pkg-constants
Denys Séguret



7
@icza Câu hỏi này đã được hỏi 3 năm trước đó. Đây không thể là một bản sao của cái đó, giả sử mũi tên thời gian đang hoạt động.
carbocation

Câu trả lời:


658

Trích dẫn từ thông số kỹ thuật ngôn ngữ: Iota

Trong một khai báo không đổi, iota định danh được khai báo trước đại diện cho các hằng số nguyên không được nối tiếp. Nó được đặt lại về 0 bất cứ khi nào từ const const const xuất hiện trong nguồn và tăng sau mỗi ConstSpec. Nó có thể được sử dụng để xây dựng một tập hợp các hằng số liên quan:

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)

Trong ExpressionList, giá trị của mỗi iota là như nhau vì nó chỉ được tăng sau mỗi 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
)

Ví dụ cuối cùng này khai thác sự lặp lại ngầm định của danh sách biểu thức không trống cuối cùng.


Vì vậy, mã của bạn có thể giống như

const (
        A = iota
        C
        T
        G
)

hoặc là

type Base int

const (
        A Base = iota
        C
        T
        G
)

nếu bạn muốn căn cứ là một loại riêng biệt từ int.


16
ví dụ tuyệt vời (tôi đã không nhớ lại hành vi iota chính xác - khi nó được tăng lên - từ thông số kỹ thuật). Cá nhân tôi thích đưa ra một loại cho một enum, vì vậy nó có thể được kiểm tra kiểu khi được sử dụng làm đối số, trường, v.v.
mna

16
Rất thú vị @jnml. Nhưng tôi hơi thất vọng vì việc kiểm tra kiểu tĩnh dường như bị lỏng lẻo, ví dụ, không có gì ngăn cản tôi sử dụng Base n ° 42 chưa từng tồn tại: play.golang.org/p/oH7eiXBxhR
Deleplace 20/1/13

4
Go không có khái niệm về các kiểu sắp xếp số, ví dụ như Pascal có, vì vậy Ord(Base)không bị giới hạn 0..3nhưng có cùng giới hạn với kiểu số cơ bản của nó. Đó là một sự lựa chọn thiết kế ngôn ngữ, thỏa hiệp giữa an toàn và hiệu suất. Xem xét kiểm tra ràng buộc thời gian chạy "an toàn" mỗi lần khi chạm vào Basegiá trị đã nhập. Hoặc làm thế nào một người nên xác định hành vi 'tràn' Basegiá trị cho mỹ phẩm và cho ++--? V.v.
zzzz

7
Để bổ sung cho jnml, thậm chí về mặt ngữ nghĩa, không có gì trong ngôn ngữ nói rằng các hằng số được xác định là Base đại diện cho toàn bộ phạm vi của Base hợp lệ, nó chỉ nói rằng các hằng số cụ thể này thuộc loại Base. Nhiều hằng số có thể được định nghĩa ở nơi khác là Base, và nó thậm chí không loại trừ lẫn nhau (ví dụ const Z Base = 0 có thể được xác định và sẽ hợp lệ).
mẹ

10
Bạn có thể sử dụng iota + 1để không bắt đầu lúc 0.
Marçal Juan

87

Đề cập đến câu trả lời của jnml, bạn có thể ngăn chặn các trường hợp mới của loại Cơ sở bằng cách không xuất khẩu loại Cơ sở (ví dụ viết chữ thường). Nếu cần, bạn có thể tạo giao diện có thể xuất có phương thức trả về loại cơ sở. Giao diện này có thể được sử dụng trong các chức năng từ bên ngoài liên quan đến Căn cứ, tức là

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
}

Bên trong gói chính a.Basercó hiệu quả như một enum bây giờ. Chỉ bên trong gói bạn có thể xác định trường hợp mới.


10
Phương thức của bạn có vẻ hoàn hảo cho các trường hợp basechỉ được sử dụng làm phương thức nhận. Nếu agói của bạn để lộ một hàm lấy tham số kiểu base, thì nó sẽ trở nên nguy hiểm. Thật vậy, người dùng chỉ có thể gọi nó với giá trị bằng chữ 42, mà hàm sẽ chấp nhận basevì nó có thể được chuyển thành int. Để ngăn chặn điều này, hãy tạo basemột struct: type base struct{value:int}. Vấn đề: bạn không thể khai báo các cơ sở là hằng số nữa, chỉ các biến mô-đun. Nhưng 42 sẽ không bao giờ được chọn vào baseloại đó.
Niriel

6
@metakeule Tôi đang cố gắng hiểu ví dụ của bạn nhưng sự lựa chọn của bạn trong các tên biến đã làm cho nó cực kỳ khó khăn.
anon58192932

1
Đây là một trong những lỗi của tôi trong các ví dụ. FGS, tôi nhận ra nó rất hấp dẫn, nhưng đừng đặt tên biến giống như kiểu!
Graham Nicholls

27

Bạn có thể làm cho nó như vậy:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

Với trình biên dịch mã này nên kiểm tra loại enum


5
Các hằng số thường được viết bằng lạc đà bình thường, không phải tất cả chữ hoa. Chữ in hoa ban đầu có nghĩa là biến đó được xuất, có thể có hoặc không phải là thứ bạn muốn.
425nesp

1
Tôi đã nhận thấy trong mã nguồn Go có một hỗn hợp trong đó đôi khi các hằng số đều là chữ hoa và đôi khi chúng là chữ hoa. Bạn có một tài liệu tham khảo cho một spec?
Jeremy Gailor

@JeremyGailor Tôi nghĩ 425nesp chỉ được lưu ý rằng sở thích bình thường là dành cho các nhà phát triển để sử dụng chúng như unexported hằng nên sử dụng camelcase. Nếu nhà phát triển xác định rằng nó nên được xuất thì hãy sử dụng tất cả chữ hoa hoặc chữ hoa vì không có ưu tiên thiết lập. Xem các khuyến nghị đánh giá mã Golangphần đi hiệu quả trên các hằng số
waynethec

Có một sở thích. Cũng giống như các biến, hàm, loại và các loại khác, tên hằng phải là hỗn hợp hoặc hỗn hợp, không phải là ALLCAPS. Nguồn: Nhận xét đánh giá mã .
Rodolfo Carvalho

Lưu ý rằng ví dụ: các hàm mong đợi MessageType sẽ vui vẻ chấp nhận các hằng số chưa được gõ, ví dụ 7. Ngoài ra, bạn có thể truyền bất kỳ int32 nào cho MessageType. Nếu bạn nhận thức được điều này, tôi nghĩ rằng đây là cách thành ngữ nhất.
Kosta

23

Đúng là các ví dụ trên về việc sử dụng constiotalà những cách thành ngữ nhất để biểu thị các enum nguyên thủy trong Go. Nhưng điều gì sẽ xảy ra nếu bạn đang tìm cách tạo ra một enum có đầy đủ tính năng tương tự như loại bạn thấy trong một ngôn ngữ khác như Java hoặc Python?

Một cách rất đơn giản để tạo một đối tượng bắt đầu trông và cảm thấy giống như một chuỗi enum trong Python sẽ là:

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)
}

Giả sử bạn cũng muốn một số phương thức tiện ích, như Colors.List(), và Colors.Parse("red"). Và màu sắc của bạn phức tạp hơn và cần thiết để trở thành một cấu trúc. Sau đó, bạn có thể làm một cái gì đó như thế này:

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())
}

Tại thời điểm đó, chắc chắn rằng nó hoạt động, nhưng bạn có thể không thích cách bạn phải xác định lại màu sắc. Nếu tại thời điểm này, bạn muốn loại bỏ điều đó, bạn có thể sử dụng các thẻ trên struct của mình và thực hiện một số phản xạ lạ mắt để thiết lập nó, nhưng hy vọng điều này là đủ để bao phủ hầu hết mọi người.


19

Kể từ Go 1.4, go generatecông cụ này đã được giới thiệu cùng với stringerlệnh giúp enum của bạn dễ dàng gỡ lỗi và có thể in được.


Bạn có biết là giải pháp đối diện. Ý tôi là chuỗi -> MyType. Vì một cách giải pháp là xa lý tưởng. Đây là sb gist làm những gì tôi muốn - nhưng viết bằng tay rất dễ mắc lỗi.
SR

11

Tôi chắc chắn chúng ta có rất nhiều câu trả lời tốt ở đây. Nhưng, tôi chỉ nghĩ đến việc thêm cách tôi đã sử dụng các kiểu liệt kê

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())
}

Đây là một trong những cách thành ngữ mà chúng ta có thể tạo ra các kiểu liệt kê và sử dụng trong Go.

Biên tập:

Thêm một cách khác để sử dụng hằng số để liệt kê

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)

}

2
Bạn có thể khai báo hằng với các giá trị chuỗi. IMO sẽ dễ dàng hơn để làm điều đó nếu bạn có ý định hiển thị chúng và thực sự không cần giá trị số.
cbednarski

4

Dưới đây là một ví dụ sẽ chứng minh hữu ích khi có nhiều bảng liệt kê. Nó sử dụng các cấu trúc ở Golang và dựa trên các Nguyên tắc hướng đối tượng để gắn kết tất cả chúng lại với nhau trong một bó nhỏ gọn gàng. Không có mã cơ bản nào sẽ thay đổi khi liệt kê mới được thêm hoặc xóa. Quá trình là:

  • Xác định cấu trúc liệt kê cho enumeration items: EnumItem . Nó có một số nguyên và kiểu chuỗi.
  • Xác định enumerationdanh sách dưới dạng enumeration items: Enum
  • Xây dựng phương pháp cho phép liệt kê. Một số đã được bao gồm:
    • enum.Name(index int): trả về tên của chỉ mục đã cho.
    • enum.Index(name string): trả về tên của chỉ mục đã cho.
    • enum.Last(): trả về chỉ mục và tên của phép liệt kê cuối cùng
  • Thêm định nghĩa liệt kê của bạn.

Đây là một số mã:

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"}}}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.