Go에서 열거 형을 나타내는 관용적 방법은 무엇입니까?


522

나는 N 개의 염기로 구성된 단순화 된 염색체를 나타내려고 노력하고 있는데, 각각은 염기 중 하나만 될 수 있습니다 {A, C, T, G}.

열거 형으로 제약 조건을 공식화하고 싶지만 열거 형을 에뮬레이트하는 가장 관용적 인 방법이 무엇인지 궁금합니다.


4
표준 패키지에서는 상수로 표시됩니다. 참조 : golang.org/pkg/os/#pkg-constants
Denys Séguret



7
@icza이 질문은 3 년 전에 요청되었습니다. 시간의 화살표가 작동 상태에 있다고 가정하면, 이것과 중복 될 수 없습니다.
carbocation

답변:


658

언어 사양에서 인용 : Iota

상수 선언 내에서 미리 선언 된 식별자 iota는 연속적인 형식화되지 않은 정수 상수를 나타냅니다. 예약어 const가 소스에 나타날 때마다 0으로 재설정되고 각 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 동작을 기억하지 못했습니다). 개인적으로 저는 열거 형에 타입을 부여하고 싶습니다. 인자, 필드 등으로 사용될 때 타입을 확인할 수 있습니다.
mna

16
매우 흥미로운 @jnml. 하지만 자료 n은 결코 존재하는 ° 42 사용하는 예를 들어, 아무것도 방지 나를 위해, 정적 형식 검사가 느슨한 것 같다 실망의 종류 해요 : play.golang.org/p/oH7eiXBxhR
Deleplace

4
이동 숫자 부분 범위 유형의 개념, 예를 들면 파스칼의가있다처럼, 그래서이 없습니다 Ord(Base)에 국한되지 않습니다 0..3하지만 기본 숫자 형식과 같은 한계가있다. 언어 디자인 선택이며 안전과 성능 사이의 타협입니다. Base입력 된 값을 터치 할 때마다 항상 "안전한"런타임 검사를 고려하십시오 . 또는 어떻게 하나의 '오버 플로우'행동을 정의해야합니다 Base를 arithmetics과 가치를 ++--? 기타
zzzz

7
jnml을 의미 적으로 보완하기 위해 언어로 Base로 정의 된 const가 유효한 Base의 전체 범위를 나타내는 것은 아니며 이러한 특정 const가 Base 유형이라고 말합니다. 더 많은 상수를 Base로 정의 할 수 있으며 상호 배타적이지도 않습니다 (예 : const Z Base = 0을 정의하고 유효 할 수 있음).
mna

10
iota + 10에서 시작하지 않으 려면 사용할 수 있습니다.
Marçal Juan

87

jnml의 답변을 참조하면 Base 유형을 전혀 내 보내지 않으면 서 Base 유형의 새 인스턴스를 방지 할 수 있습니다 (즉, 소문자로 작성). 필요한 경우 기본 유형을 반환하는 메서드가있는 내보내기 가능한 인터페이스를 만들 수 있습니다. 이 인터페이스는베이스를 다루는 외부 기능에서 사용될 수 있습니다.

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는 사실상 열거 형과 같습니다. 패키지 내부에서만 새 인스턴스를 정의 할 수 있습니다.


10
base메소드 리시버로만 사용되는 경우에는 메소드가 완벽 해 보입니다 . 귀하의 경우 a패키지 유형의 매개 변수를 복용하는 기능을 노출했다 base, 그것은 위험 할 것입니다. 실제로 사용자는 리터럴 값 42를 사용하여 호출 할 base수 있습니다.이 값은 int로 캐스팅 될 수 있으므로 함수가 허용 합니다. 이를 방지하기 위해 만들 basestruct: type base struct{value:int}. 문제 : 더 이상베이스를 상수로 선언 할 수없고 모듈 변수 만 선언 할 수 있습니다. 그러나 42는 결코 base그런 유형 으로 캐스팅되지 않습니다 .
Niriel

6
@metakeule 나는 당신의 예를 이해하려고 노력하고 있지만 변수 이름에서의 선택은 그것을 매우 어렵게 만들었습니다.
anon58192932

1
이것은 예제에서 내 버그 베어 중 하나입니다. FGS, 나는 유혹하고 있다는 것을 알고 있지만 변수와 유형의 이름을 동일하게 지정하지 마십시오!
Graham Nicholls

27

당신은 그렇게 할 수 있습니다 :

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

이 코드로 컴파일러는 열거 형의 유형을 확인해야합니다


5
상수는 일반적으로 모두 대문자가 아닌 일반 낙타로 작성됩니다. 초기 대문자는 변수가 내보내 짐을 의미하며 원하는 변수 일 수도 있고 아닐 수도 있습니다.
425nesp December

1
Go 소스 코드에는 상수가 모두 대문자이며 때로는 낙타가 혼합되어 있음을 알았습니다. 사양에 대한 참조가 있습니까?
Jeremy Gailor

@JeremyGailor 나는 425nesp 단지 개발자로 사용하는 일반적인 환경 설정이라고 지적 생각 안 export 사용의 camelcase 때문에 상수. 개발자가 내 보내야한다고 결정한 경우 기본 설정이 없으므로 대문자 나 대문자를 자유롭게 사용하십시오. 상수에 대한 Golang 코드 검토 권장 사항효과적인 이동 섹션
waynethec

선호 사항이 있습니다. 변수, 함수, 유형 및 기타와 마찬가지로 상수 이름은 ALLCAPS가 아니라 mixedCaps 또는 MixedCaps 여야합니다. 출처 : Go Code Review Comments .
Rodolfo Carvalho

예를 들어 MessageType을 기대하는 함수는 형식이 지정되지 않은 숫자 const를 행복하게 받아들입니다 (예 : 7). 또한 모든 int32를 MessageType으로 캐스트 할 수 있습니다. 당신이 이것을 알고 있다면, 이것이 가장 관용적 인 방법이라고 생각합니다.
Kosta

23

그것은 사용하는 위의 예 것은 사실 constiota이동에 원시적 열거를 나타내는 가장 관용적 가지 방법이 있습니다. 그러나 Java 또는 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열거 형을 쉽게 디버깅하고 인쇄 할 수 있는 명령 과 함께 도입되었습니다 .


oposite 솔루션이라는 것을 알고 있습니까? 나는 문자열-> 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를 표시하고 실제로 숫자 값이 필요하지 않은 경우 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.