Go
golang

Go 1.9 の型エイリアスを使って偽 Generics の実装

More than 1 year has passed since last update.

はじめに

  • Go 1.9 から入った型エイリアス機能を使ってコード生成や refrect を使わない簡単な偽 Generics を実装してみる

どうやるか

  • 任意の型 t1 を扱う関数などが定義されたファイルを用意する
  • その関数を使いたいパッケージにそのファイルをコピーする
  • そのパッケージの型 P1type t1 = P1 と型エイリアスの定義をすることで先のファイルで定義された関数を P1 から利用できる

コード

任意の型を扱う関数の定義されたファイル

json.go
// t1 型の定義は外部のファイルに任せる

func Marshal(t *t1) (string, error) {
    j, err := json.Marshal(t)
    return string(j), err
}

func Unmarshal(s string) (*t1, error) {
    var t *t1
    err := json.Unmarshal([]byte(s), &t)
    return t, err
}

先のファイルを利用するファイル

user.go
type User struct {
    Name string `json:"name"`
    Age int `json:"age"`
}

func (u *User) Hello() string {
    return "hello " + u.Name
}

// ここで json.go で定義された型のエイリアスを設定する
type t1 = User

テスト

user_test.go
func TestMarshal(t *testing.T) {
    u := &User{Name: "bar", Age: 100}
    j, err := Marshal(u)
    if err != nil {
        t.Error(err)
    }
    if j != `{"name":"bar","age":100}` {
        t.Error("invalid json: " + j)
    }
}

func TestUnmarshal(t *testing.T) {
    j := `{"name":"bar","age":100}`
    u, err := Unmarshal(j)
    if err != nil {
        t.Error(err)
    }
    if u.Hello() != "hello bar" {
        t.Errorf("invalid user: %v", u)
    }
}

メリット

  • こうすることでミックスインのように json.go の関数が実行できるようになる
  • json.go 単体で別のパスにおいておいて任意の型エイリアスを定義することでそのままテストが実行できる
  • go generate を使って json.go をコピーしてくるようにすれば json.go が更新されても追従できる

いつつかうか

  • コード生成とか reflect とかめんどくさいけど手軽に Generics っぽいことがしたいときに使えるかも
  • 1.9 未満でも type t1 User として適宜キャストすれば同じパターンでできなくもない
    • キャストするだけコードは増えるので面倒ではある