Go言語でジェネリクスっぽいことがしたいでござる【generics】【golang】
Go言語でジェネリクスみたいなことがしたいでござる。
色々調査のうえでやってみた。
stringからインスタンスを取得
完全に抽象化は無理で、具体レイヤーで必ず型アサーションをしなきゃいけない。
型アサーションの例
a := new(A) b, ok := reflect.ValueOf(a).Interface().(B) // ok == false
ジェネリクスみたいなものがあれば、この限りではないと思うんだけど、Go言語にジェネリクスは無い。
ということで以下参考とメモ
- go - Golang: How to instantiate
struct
by struct name (string) [duplicate] - go - is there a way to create an instance of a struct from a string? - Stack Overflow
package main import "fmt" import "reflect" var typeRegistry = map[string]reflect.Type {} func Register(v interface{}) { typeRegistry[reflect.TypeOf(v).String()] = reflect.TypeOf(v) } func Of(typeName string) reflect.Type { return typeRegistry[typeName] } type A struct { Name string } func (a *A) Greet() { fmt.Printf("Hi, I'm %s.\n", a.Name) } func main() { Register(A{}) fmt.Printf("All types registered...\n%+v\n", typeRegistry) // x := reflect.New(Of("main.A")) // x.Greet() // because it's "*reflect.Value" // x := reflect.New(Of("main.A")).Elem() // x.Greet() // because it's "reflect.Value" // x := reflect.New(Of("main.A")).Interface() // x.Greet() // because its' "interface {}" // x := reflect.New(Of("main.A")).Elem().Interface() // x.Greet() // because it's "interface {}" x := reflect.New(Of("main.A")).Elem().Interface().(A) x.Greet() // It works (but name is "") y := reflect.New(Of("main.A")).Interface().(*A) y.Greet() // It works (but also name is "") }
型アサーションにかけないとそのstructが持ってるメソッドにアクセスできないことがわかる。
いったん抽象化して持っとくラッパーに入れてから具体化する
もちろんこの場合でも具体レイヤーで型アサーションをする必要があるのだけれど、eがEであると知っていなければならないレイヤーでやればいいだけなので、あまり問題は無いかもしれない。
メモ
package main import "fmt" import "reflect" type Holder struct { v interface{} } func (h Holder) Type() string { return reflect.TypeOf(h.v).String() } func (h Holder) Get() interface{} { return h.v } type E struct { Name string } func (e E) Greet() { fmt.Printf("Hi, I'm %s.", e.Name) } func NewHolder(v interface{}) Holder { return Holder{v} } func main() { holder := NewHolder(E{"otiai10"}) fmt.Printf("This holder holds %s\n", holder.Type()) e := holder.Get().(E) e.Greet() }
ということは以下のようにジェネリクスっぽいことが
できるのでは、と思ったのでやってみた
package main import "fmt" import "reflect" type Collection struct { representative interface{} elements []interface{} } func (c *Collection) Count() (count int) { count = len(c.elements) return } func (c *Collection) TypeOf() string { return reflect.TypeOf(c.representative).String() } func (c *Collection) Push(el interface{}) (ok bool) { if reflect.TypeOf(el).String() == reflect.TypeOf(c.representative).String() { c.elements = append(c.elements, el) ok = true } return ok } func (c *Collection) Find(i int) (v interface{}) { if i < len(c.elements) { return c.elements[i] } return } func NewCollection(v interface{}) *Collection { return &Collection{ representative: v, } } type Person struct { Name string } func (p *Person) Greet() { fmt.Printf("Hi, I'm %s.\n", p.Name) } func main() { col := NewCollection("some string") col.Push("foo") col.Push("bar") col.Push("buz") fmt.Println( col.Find(0), col.Find(1), col.Find(2), ) // ただし、Findの結果はinterface{}であるので、 // col.TypeOf()で得られるような型の本来持つべき // メソッドを持たない // たとえば col = NewCollection(&Person{}) col.Push(&Person{"田井中律"}) col.Push(&Person{"真鍋和"}) // col.Find(0).Greet() // col.Find(0).Greet undefined (type interface {} has no field or method Greet) // これはCollection.Findの返り値型がinterface{}なのでしょうがない // Greetメソッドを使いたければ型アサーションをするしかない ritsu, ok := col.Find(0).(*Person) if ok { ritsu.Greet() } }
まあだいたい動くし、いっか
というか型に型変数を渡して型に固定できないのならそれはジェネリクスとは言わないんじゃないかな...
まいっか。
DRYな備忘録