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さんありがとうございます!

Recent open source activities

pmd

https://github.com/pmd/pmd/pull/44

CPD (Copy Paste Detector) supported GoLang.

go-xorm

https://github.com/go-xorm/xorm/issues/166

Suggested and implemented "soft delete" for xorm. My pull-request was immediately merged! (very impressive)

goose

https://bitbucket.org/liamstask/goose/pull-request/43/support-mysql-driver

Added mysql driver support. My pull-request hasn't been merged yet. I contacted to the author but he didn't respond.

gunicornをローカル開発環境のWebサーバとして使う

--reloadオプションがバージョン19.0からサポートされたので、下記のような感じで起動するとローカルの開発サーバとして使える。

$ gunicorn --access-logfile - --log-file - --reload -b 127.0.0.1:3000 -w 1 app:app
  • --access-logfile: "-"を指定するとstderrにアクセスログが出る
  • --log-file: ↑と同様に"-"を指定するとstderrにエラーログが出る
  • --reload: ソースコードが変更されたらリロードする
  • -b: bindするアドレスとポートの指定
  • -w: ワーカー数

Python文法詳解

Python文法詳解

Goでnilなsliceやmapを返すと空のsliceやmapになる

タイトルの通りだけど知らなかったので。明示的に空のsliceやmapを作らなくてもいいのは楽だ。

http://play.golang.org/p/veOWHGDdcn

package main

import "fmt"

func emptyMap() map[string]string {
    return nil
}

func emptySlice() []string {
    return nil
}

func main() {
    emptySlice := emptySlice()
    fmt.Printf("slice len = %d\n", len(emptySlice))

    emptyMap := emptyMap()
    fmt.Printf("map len = %d\n", len(emptyMap))
}

基礎からわかる Go言語

基礎からわかる Go言語