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


522

私は、N塩基からなる簡略化された染色体を表現しようとしています。N塩基はそれぞれの1つだけです{A, C, T, G}

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


4
go標準パッケージでは、定数として表されます。参照してくださいgolang.org/pkg/os/#pkg-constants
デニス・Séguret


関連/ Golangの
icza

7
@iczaこの質問はその3年前に行われました。時間の矢が正常に機能していることを前提として、これをその複製とすることはできません。
カルボ

列挙型に行くための究極のガイドをチェックしてください。
Inanc Gumus

回答:


658

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

定数宣言内では、事前宣言された識別子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
素晴らしい例(私はイオタの正確な振る舞い-インクリメントされたとき-スペックから思い出しませんでした)個人的に私は、引数など、フィールドとして使用する場合には、タイプチェックできるので、列挙型にタイプを与えたい
MNA

16
非常に興味深い@jnml。しかし、静的な型チェックが緩やかであるように思われるのはちょっとがっかりです。たとえば、存在しなかったBase n°42の使用を妨げるものは何もありません。play.golang.org
p

4
Goには、たとえばPascalのhasのような数値のサブレンジタイプの概念Ord(Base)がないため0..3、基になる数値タイプと同じように制限されません。これは言語設計の選択であり、安全性とパフォーマンスの妥協点です。Base入力された値に触れるときは常に「安全な」ランタイムバウンドチェックを検討してください。またはBase、算術およびfor ++とfor の値の「オーバーフロー」動作をどのように定義する必要があり--ますか?その他
zzzz

7
jnmlを補足するために、意味的にも、言語として、Baseとして定義されたconstが有効なBaseの範囲全体を表すということはなく、これらの特定のconstがBase型であると述べているだけです。より多くの定数を他の場所でBaseとして定義することもでき、相互に排他的でさえありません(たとえば、const Z Base = 0を定義して有効にすることができます)。
2013年

10
iota + 10で始まらないように使用できます。
MarçalJuan 2014

87

jnmlの答えを参照すると、Baseタイプをまったくエクスポートしない(つまり、小文字で書き込む)ことにより、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パッケージがtypeのパラメーターを取る関数を公開する場合、baseそれは危険になります。実際、ユーザーはそれをリテラル値42で呼び出すことができます。これはbase、intにキャストできるため、関数はこれを受け入れます。これを防ぐにはbasestruct:を作成しますtype base struct{value:int}。問題:基底を定数として宣言することはできなくなり、モジュール変数のみになります。ただし、42がbaseそのタイプのにキャストされることはありません。
Niriel 2013

6
@metakeule私はあなたの例を理解しようとしていますが、変数名の選択が非常に困難にしています。
anon58192932 2017年

1
これは、私のバグベアの例の1つです。FGS、私はそれが魅力的であることを理解していますが、変数に型と同じ名前を付けないでください!
Graham Nicholls

27

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

type MessageType int32

const (
    TEXT   MessageType = 0
    BINARY MessageType = 1
)

このコードを使用すると、コンパイラは列挙型をチェックする必要があります


5
定数は通常、すべて大文字ではなく、通常のキャメルケースで記述されます。最初の大文字は、変数がエクスポートされることを意味します。これは必要な場合とそうでない場合があります。
425nesp 2017

1
Goのソースコードで、定数がすべて大文字でキャメルケースである場合があることに気づきました。仕様への参照はありますか?
ジェレミーゲイラー2018年

@JeremyGailor 425nespは、開発者がそれらをエクスポートされていない定数として使用するのが通常の設定なので、キャメルケースを使用することを指摘しているだけだと思います。開発者がそれをエクスポートする必要があると判断した場合は、設定が確立されていないため、すべて大文字または大文字を自由に使用してください。Golangコードレビューの推奨事項定数に関する効果的なGoセクションを
waynethec 2018年

好みがあります。変数、関数、型などと同様に、定数名はALLCAPSではなく、mixedCapsまたはMixedCapsにする必要があります。出典:Go Code Reviewのコメント
ロドルフォカルバーリョ

たとえば、MessageTypeを期待する関数は、型なしの数値定数(7など)を喜んで受け入れます。さらに、任意のint32をMessageTypeにキャストできます。これに気づいているなら、これが最も慣用的な方法だと思います。
コスタ

23

これは、使用しての上記の例というのは本当だconstiotaゴーで原始的列挙型を表現する最も慣用的な方法です。しかし、JavaやPythonなどの別の言語で見られる型に似た、より完全な機能を持つ列挙型を作成する方法を探している場合はどうでしょうか。

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、列挙型を簡単にデバッグおよび印刷可能にするコマンドとともに導入されました。


あなたは反対の解決策であることを知っていますか?文字列-> 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で使用できる慣用的な方法の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)

}

2
文字列値を使用して定数を宣言できます。IMO表示するつもりで、実際には数値が必要ない場合は、その方が簡単です。
cbednarski

4

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

  • 以下のために列挙構造を定義しますenumeration itemsEnumItem。整数型と文字列型があります。
  • enumerationリストとして定義しますenumeration items列挙型
  • 列挙のためのメソッドを作成します。いくつか含まれています:
    • 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.