はじめに
- GAE/Go + Datastore 向けの汎用レポジトリライブラリみたいなものを作ってみて Go で型を抽象的に扱うコードをいくらか書いたのでまとめてみる。
Go で型を抽象的に扱うには
- Go では reflect と interface を使って型を抽象的に扱うことができる
- Go で型を抽象的に扱う時に出てくる登場人物としては、抽象的に値を扱うことができる
reflect.Value
と抽象的に型を扱うことができるreflect.Type
と任意の値を扱うことができるinterface{}
がある - ここで Go の値(便宜的に
Go value
と表記)とそれぞれの登場人物の相関図とそれぞれの操作を表すコードを記載する
package main
import (
"reflect"
"log"
)
func main() {
// 1. Go value から reflect.Value へ変換する
gv := 1
rv := reflect.ValueOf(gv)
p("ValueOf.", rv)
// 2. reflect.Value から Go value へ変換する
// Int, Float, String など組み込みの型のみ関数が用意されている
gv2 := rv.Int()
p("Int.", gv2)
// 3. reflect.Value から interface{} へ変換する
i := rv.Interface()
p("interface{}.", i)
// 4. interface{} から Go value へ変換する
// Type Assertion を使う
if gv, ok := i.(int); ok {
p("Go value.", gv)
}
// 5. Go value から reflect.Type を取得する
rt := reflect.TypeOf(gv)
p("Type.", rt)
// 6. reflect.Value から reflect.Type を取得する
rt = rv.Type()
p("Type.", rt)
// 7. reflect.Type から任意の型の reflect.Value を作成する
// []int の reflect.Type を作成する
srt := reflect.SliceOf(rt)
// []int の reflect.Value を作成する
srv := reflect.MakeSlice(srt, 0, 0)
// []int の reflect.Value に append する
srv = reflect.Append(srv, reflect.ValueOf(100), reflect.ValueOf(200))
// reflect.Value を interface{} に変換して Go value に変換する
if s, ok := srv.Interface().([]int); ok {
p("MakeSlice.", s)
}
}
func p(prefix string, val interface{}) {
rt := reflect.TypeOf(val)
log.Printf(prefix + "\nvalue: %+v, type: %+v, kind: %+v", val, rt, rt.Kind())
}
- Play ground での実行はこちら https://play.golang.org/p/akK9ho9swL
型を抽象的に扱うといえば Generics
- Go には Generics はないが Generics がなくても何とかなる部分とやっぱりあると嬉しい部分も見えてきた。
Generics がなくても何とかなる部分
- interface{} を使えば任意の値を扱うことができる。
- reflect.Type を使えば型のメタデータにアクセスして型に合わせた柔軟な処理を記述できる。
やっぱり Generics があると嬉しい部分
- interface{} から Go value への変換(上記の図の 4 の部分)でどうしても具体的な型が必要になるのでここだけ抽象化できずにライブラリなんかにまとめることができない。
- reflect.Type で型チェックをする場合、実行時に行われるため型安全ではなくなる。
型アサーションが抽象化できない問題への対応
- 諦めて型アサーションする
- 型アサーションをするラッパーを都度実装する
型チェックが実行時に行われる問題への対応
- 型安全にするならダックタイピングで
- インターフェイスに合わせた実装が必要なので気軽には扱えない
- ライブラリで扱うならありかも
Generics は必要か?
- あると便利で、なくてもある程度はできるのがわかってきた。
- ダックタイピングを利用すれば型安全にある程度のことはできる。
- しかし気軽に利用できるような何かがあると嬉しいので、この部分が Go は筋力が必要と言われる部分。
- 型アサーションが reflect.Type でできるとか reflect 内で Go value へのキャストまでできるようになれば、個人的には十分な気がしている。