oinume journal

Scratchpad of what I learned

GoLangでJavaのenumっぽいライブラリ作った話

はじめに

Goでは言語標準でenumという機構がなくてつらかったのでそれっぽいものを作ったよという話。

Goではiotaを使って以下のようにして定数を列挙することはできる。

const (
    Go int = iota + 1
    Python
    Ruby
)

ただし、このやり方だとJavaのenumのように

  1. 値から名前(↑でいう"Java"という定数名)を取得したり、名前からその値を引く
  2. 全ての値を列挙する
  3. 全ての名前を列挙する

というようなことができない。下記のように値→名前を取得する関数を定義することはできるので1.はなんとかなるわけなんだけど、これを毎回書くのは面倒なので goenum というライブラリを作ってみたよという話(ここからが本題)。

http://play.golang.org/p/5nrPOMEQKZ

package main

import (
    "fmt"
)

type Lang int

func (l Lang) String() string {
    switch l {
    case Go:
        return "Go"
    case Python:
        return "Python"
    case Ruby:
        return "Ruby"
    }
    panic("Unknown value")
}

const (
    Go Lang = iota + 1
    Python
    Ruby
)

func main() {
    fmt.Printf("%s: %d\n", Go.String(), Go)
    fmt.Printf("%s: %d\n", Python.String(), Python)
    fmt.Printf("%s: %d\n", Ruby.String(), Ruby)
}

goenum

goenumでは列挙する値はconstではなくstructを使って定義する。

type LangsEnum struct {
    Go     int
    Python int
    Ruby   int
    Haskel int
}

でで、その構造体(↑でいうLangsEnum)に下記のようなEnum()というメソッドを定義しておき、goenum.EnumerateStruct という関数にLangsEnumの実体を渡す。goenum.EnumerateStruct() という関数は goenum.Enum を返すんだけど、これが

  • 値→名前
  • 名前→値

の変換を行ってくれる。

var Langs LangsEnum = LangsEnum{1, 2, 3, 4}

func (e LangsEnum) Enum() goenum.Enum {
    // Make Enum from Langs
    return goenum.EnumerateStruct(&Langs)
}

以下は実際に使ってみた例。

func main() {
    var langs goenum.Enum = Langs.Enum()

    // 名前の列挙
    fmt.Println(langs.Names())
    // --> [Go Python Ruby Haskel]

    // 値の列挙
    fmt.Println(langs.Values())
    // --> [1 2 3 4]

    // 値 -> 名前の取得
    fmt.Println(langs.MustName(1))
    // --> Go

    // 名前 -> 値の取得
    fmt.Println(langs.MustValue("Python"))
    // --> 2
}

本当は構造体で元の列挙値を定義するやり方にはしたくなかったんだけど、自分のGo力が低すぎるのでこういう仕上がりになっているので、もっと良いやり方があったらぜひ教えて欲しいところです。

追記

Big Sky :: Re: GoLangでJavaのenumっぽいライブラリ作った話でstringerというコマンドを使う方法を教えてもらいました。1.4では go generate コマンドもあるし、これ使ってgenerateするのがIdiomatic wayのような気がしますね。mattnさんありがとうございます!