はじめに
- Go 1.9 から入った型エイリアス機能を使ってコード生成や refrect を使わない簡単な偽 Generics を実装してみる
どうやるか
- 任意の型
t1
を扱う関数などが定義されたファイルを用意する - その関数を使いたいパッケージにそのファイルをコピーする
- そのパッケージの型
P1
をtype 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
として適宜キャストすれば同じパターンでできなくもない- キャストするだけコードは増えるので面倒ではある