วิธีการแสดงความเป็นตัวตนของ Enums ใน Go คืออะไร?


522

ฉันพยายามที่จะเป็นตัวแทนของโครโมโซมง่ายซึ่งประกอบด้วยฐาน N {A, C, T, G}ซึ่งแต่ละเพียงสามารถเป็นหนึ่งใน

ฉันต้องการทำให้เป็นข้อ จำกัด ด้วย enum อย่างเป็นทางการ แต่ฉันสงสัยว่าการเลียนแบบ enum เป็นวิธีที่งี่เง่าที่สุดใน Go


4
แพคเกจมาตรฐานแบบพกพาจะแสดงเป็นค่าคงที่ ดูgolang.org/pkg/os/#pkg-constants
Denys Séguret

2
ที่เกี่ยวข้อง: stackoverflow.com/questions/14236263/...
lbonn

ซ้ำซ้อนที่เกี่ยวข้อง / เป็นไปได้ของGolang: การสร้างประเภทคงที่และการ จำกัด ค่าของประเภท
icza

7
@icza คำถามนี้ถูกถามเมื่อ 3 ปีก่อน สิ่งนี้ไม่สามารถซ้ำกันได้เนื่องจากสมมติว่าลูกศรของเวลาอยู่ในสภาพที่ใช้งานได้
carbocation

คำตอบ:


658

การอ้างอิงจากข้อกำหนดภาษา: Iota

ภายในการประกาศค่าคงที่ตัวระบุที่ประกาศไว้ล่วงหน้า iota แสดงค่าคงที่จำนวนเต็มที่ยังไม่ได้พิมพ์ต่อเนื่อง มันจะถูกรีเซ็ตเป็น 0 เมื่อใดก็ตามที่ const คำสงวนปรากฏขึ้นในแหล่งที่มาและเพิ่มขึ้นหลังจากแต่ละ ConstSpec มันสามารถใช้ในการสร้างชุดของค่าคงที่ที่เกี่ยวข้อง:

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


16
ตัวอย่างที่ดี (ฉันไม่จำพฤติกรรม iota ที่แน่นอน - เมื่อมันเพิ่มขึ้น - จากสเป็ค) ส่วนตัวฉันชอบที่จะให้ประเภทกับ enum ดังนั้นจึงสามารถตรวจสอบประเภทเมื่อใช้เป็นอาร์กิวเมนต์เขตข้อมูลและอื่น ๆ
mna

16
@jnml ที่น่าสนใจมาก แต่ฉันรู้สึกผิดหวังที่การตรวจสอบชนิดคงที่ดูเหมือนจะหลวมตัวอย่างเช่นไม่มีอะไรป้องกันฉันจากการใช้ Base n ° 42 ซึ่งไม่เคยมีอยู่: play.golang.org/p/oH7eiXBxhR
Deleplace

4
Go ไม่มีแนวคิดของประเภทย่อยที่เป็นตัวเลขเช่นเช่น Pascal มีดังนั้นจึงOrd(Base)ไม่ จำกัด0..3แต่มีข้อ จำกัด เช่นเดียวกับประเภทตัวเลขที่เป็นรากฐาน มันเป็นตัวเลือกการออกแบบภาษาประนีประนอมระหว่างความปลอดภัยและประสิทธิภาพ พิจารณาขอบเขตเวลารัน "ปลอดภัย" ตรวจสอบทุกครั้งเมื่อแตะBaseค่าที่พิมพ์ หรือวิธีหนึ่งควรกำหนดพฤติกรรมของ 'ล้น' ของBaseค่าสำหรับ arithmetics และสำหรับ++และ--? อื่น ๆ
zzzz

7
เพื่อเติมเต็มบน jnml แม้จะมีความหมายไม่มีอะไรในภาษาที่บอกว่า const ที่กำหนดเป็น Base แสดงถึงช่วงทั้งหมดของ Base ที่ถูกต้องมันเพิ่งบอกว่า const เหล่านี้โดยเฉพาะเป็นประเภท Base ค่าคงที่เพิ่มเติมสามารถกำหนดที่อื่นเป็นฐานด้วยและไม่ได้เป็นพิเศษร่วมกัน (เช่น const Z Base = 0 สามารถกำหนดและจะถูกต้อง)
mna

10
คุณสามารถใช้iota + 1เพื่อไม่ให้เริ่มต้นที่ 0
Marçal Juan

87

อ้างถึงคำตอบของ jnml คุณสามารถป้องกันอินสแตนซ์ใหม่ของประเภทฐานโดยไม่ส่งออกประเภทฐานเลย (เช่นเขียนเป็นตัวพิมพ์เล็ก) หากจำเป็นคุณสามารถสร้างอินเทอร์เฟซที่ส่งออกได้ซึ่งมีวิธีการคืนค่าประเภทฐาน อินเทอร์เฟซนี้สามารถใช้ในฟังก์ชั่นจากภายนอกที่จัดการกับฐานคือ

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ได้อย่างมีประสิทธิภาพเหมือน enum ในขณะนี้ ภายในแพ็คเกจเท่านั้นที่คุณสามารถกำหนดอินสแตนซ์ใหม่ได้


10
ดูเหมือนว่าวิธีการของคุณจะสมบูรณ์แบบสำหรับเคสที่baseใช้เป็นตัวรับเมธอดเท่านั้น หากaแพ็กเกจของคุณต้องแสดงฟังก์ชั่นที่รับพารามิเตอร์เป็นประเภทbaseมันจะเป็นอันตราย แน่นอนผู้ใช้สามารถเรียกมันด้วยค่าตัวอักษร 42 ซึ่งฟังก์ชั่นจะยอมรับได้baseเนื่องจากมันสามารถส่งไปยัง int เพื่อป้องกันการนี้ทำให้: ปัญหา: คุณไม่สามารถประกาศฐานเป็นค่าคงที่อีกต่อไปเพียงตัวแปรโมดูล แต่ 42 จะไม่ถูกส่งไปยังประเภทนั้น basestructtype base struct{value:int}base
Niriel

6
@metakeule ฉันพยายามเข้าใจตัวอย่างของคุณ แต่การเลือกชื่อตัวแปรของคุณทำให้มันยากเหลือเกิน
anon58192932

1
นี่เป็นหนึ่งในข้อผิดพลาดของฉันในตัวอย่าง FGS ฉันรู้ว่ามันดึงดูด แต่อย่าตั้งชื่อตัวแปรเหมือนกับประเภท!
เกรแฮมนิโคลส์

27

คุณสามารถทำได้:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

ด้วยคอมไพเลอร์รหัสนี้ควรตรวจสอบประเภทของ enum


5
ค่าคงที่มักจะเขียนใน camelcase ปกติไม่ใช่ตัวพิมพ์ใหญ่ทั้งหมด ตัวอักษรตัวพิมพ์ใหญ่เริ่มต้นหมายถึงตัวแปรที่ถูกส่งออกซึ่งอาจเป็นหรืออาจไม่ใช่สิ่งที่คุณต้องการ
425

1
ฉันสังเกตเห็นในซอร์สโค้ดของ Go มีส่วนผสมที่บางครั้งค่าคงที่เป็นตัวพิมพ์ใหญ่ทั้งหมดและบางครั้งพวกมันก็เป็น camelcase คุณมีการอ้างอิงถึงข้อมูลจำเพาะหรือไม่?
Jeremy Gailor

@JeremyGailor ผมคิดว่า 425nesp เป็นเพียงการสังเกตว่าการตั้งค่าปกติสำหรับนักพัฒนาเพื่อใช้พวกเขาเป็นunexportedค่าคงที่เพื่อให้การใช้งาน CamelCase หากนักพัฒนาระบุว่าควรส่งออกคุณสามารถใช้ตัวพิมพ์ใหญ่หรือตัวพิมพ์ใหญ่ทั้งหมดได้เนื่องจากไม่มีค่ากำหนดที่กำหนดไว้ ดูคำแนะนำการตรวจสอบโค้ด Golangและ หัวข้อ Go ที่มีประสิทธิภาพในค่าคงที่
waynethec

มีการตั้งค่า เช่นเดียวกับตัวแปรฟังก์ชันประเภทและอื่น ๆ ชื่อคงที่ควรเป็น MixedCaps หรือ MixedCaps ไม่ใช่ ALLCAPS ที่มา: รหัสไปตรวจสอบความคิดเห็น
Rodolfo Carvalho

โปรดทราบว่าฟังก์ชั่นเช่นที่คาดว่า MessageType จะยินดีรับ const ตัวเลขที่ไม่ได้พิมพ์เช่น 7 นอกจากนี้คุณสามารถส่ง int32 ไปยัง MessageType ได้ หากคุณรู้เรื่องนี้ฉันคิดว่านี่เป็นวิธีที่งี่เง่าที่สุดในการเดินทาง
Kosta

23

มันเป็นความจริงที่ตัวอย่างข้างต้นของการใช้constและiotaเป็นวิธีที่ใช้สำนวนที่สุดในการเป็นตัวแทนดั้งเดิมใน En Go แต่ถ้าคุณกำลังมองหาวิธีในการสร้าง enum ที่มีคุณลักษณะครบถ้วนคล้ายกับประเภทที่คุณเห็นในภาษาอื่นเช่น Java หรือ Python

วิธีง่ายๆในการสร้างวัตถุที่เริ่มมีรูปลักษณ์และรู้สึกเหมือนสตริง enum ใน 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())
}

ถึงจุดนั้นให้แน่ใจว่าใช้งานได้ แต่คุณอาจไม่ชอบวิธีการกำหนดสีซ้ำ ๆ หาก ณ จุดนี้คุณต้องการกำจัดสิ่งนั้นคุณสามารถใช้แท็กในโครงสร้างของคุณและทำบางสิ่งบางอย่างที่สะท้อนถึงการตั้งค่า แต่หวังว่านี่จะเพียงพอที่จะครอบคลุมคนส่วนใหญ่


19

ในฐานะของ Go 1.4 go generateเครื่องมือได้รับการแนะนำพร้อมกับstringerคำสั่งที่ทำให้ enum ของคุณสามารถ debuggable และพิมพ์ได้อย่างง่ายดาย


คุณรู้หรือไม่ว่าเป็นวิธีแก้ปัญหาแบบตรงข้าม ฉันหมายถึงสตริง -> MyType เนื่องจากวิธีการแก้ปัญหาเดียวยังห่างไกลจากอุดมคติ นี่คือสาระสำคัญ sb ที่ทำในสิ่งที่ฉันต้องการ - แต่การเขียนด้วยมือเป็นเรื่องง่ายที่จะทำผิดพลาด
SR

11

ฉันแน่ใจว่าเรามีคำตอบที่ดีมากมายที่นี่ แต่ฉันแค่คิดว่าจะเพิ่มวิธีที่ฉันใช้ประเภทที่แจกแจง

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)

}

2
คุณสามารถประกาศค่าคงที่ด้วยค่าสตริง IMO ง่ายกว่าถ้าคุณต้องการแสดงและไม่ต้องการค่าตัวเลข
cbednarski

4

นี่คือตัวอย่างที่จะพิสูจน์ว่ามีประโยชน์เมื่อมีการแจกแจงจำนวนมาก มันใช้โครงสร้างใน Golang และดึงเอาหลักการเชิงวัตถุมารวมเข้าด้วยกันเป็นมัดเล็ก ๆ อย่างเรียบร้อย ไม่มีรหัสอ้างอิงใด ๆ ที่จะเปลี่ยนแปลงเมื่อมีการเพิ่มหรือลบการแจงนับใหม่ กระบวนการคือ:

  • กำหนดโครงสร้างการแจงนับสำหรับenumeration items: EnumItem มันมีประเภทจำนวนเต็มและสตริง
  • กำหนดenumerationรายการenumeration items: 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 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"}}}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.