web-dev-qa-db-ja.com

Goで列挙型を表現する慣用的な方法は何ですか?

私は単純化された染色体を表現しようとしています。それはN個の塩基で構成され、それぞれが{A, C, T, G}の1つにしかなれません。

列挙型を使用して制約を形式化したいのですが、列挙型をエミュレートする最も慣用的な方法はGoにあるのでしょうか。

440
carbocation

言語仕様からの引用: イオタ

定数宣言内では、事前宣言されている識別子iotaは連続した型なし整数定数を表します。予約語constがソースに表示され、ConstSpecごとに増加するたびに0にリセットされます。これを使用して、関連する一連の定数を作成できます。

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
)

baseをintとは別の型にしたい場合.

571
zzzz

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は事実上enumのようになっています。パッケージの中だけで新しいインスタンスを定義できます。

76
metakeule

Go 1.4以降、go generateツールは stringer コマンドとともに導入され、列挙型を簡単にデバッグおよび印刷できるようになりました。

17
Moshe Revah

あなたはそれをそうすることができます:

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

このコードでコンパイラはenumの型をチェックするべきです

16
Azat

constiotaを使用する上記の例は、Goでプリミティブ列挙体を表現する最も慣用的な方法です。しかし、JavaやPythonのような他の言語で見られるタイプに似た、より機能豊富なenumを作成する方法を探しているならどうでしょうか。

Pythonで文字列enumのように見え、感じるようになるオブジェクトを作成するための非常に簡単な方法は次のようになります。

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

その時点では、確かにうまくいきますが、色を繰り返し定義する方法が気に入らない場合があります。この時点であなたがそれを排除したいのなら、あなたはあなたの構造体の上にタグを使ってそれを設定するためにいくつかの空想を反映することができます、しかしうまくいけばこれはほとんどの人をカバーするのに十分です。

5
Becca Petrin

ここにはたくさんの良い答えがあると思います。しかし、列挙型の使い方を追加することを考えただけです。

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で使用できる慣用的な方法の1つです。

編集する

列挙に定数を使用する別の方法を追加する

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)

}
3
wandermonk

列挙が多数ある場合に役立つ例を次に示します。 Golangの構造を使用し、オブジェクト指向の原則に基づいて、それらをすべてきちんとした小さな束に結び付けます。新しい列挙が追加または削除されても、基礎となるコードは変更されません。プロセスは次のとおりです。

  • enumeration itemsの列挙構造を定義します:EnumItem。整数型と文字列型があります。
  • enumerationenumeration 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"}}}
2
Aaron