DRYな備忘録

Don't Repeat Yourself.

Go言語でジェネリクスっぽいことがしたいでござる【generics】【golang】

Go言語でジェネリクスみたいなことがしたいでござる。

色々調査のうえでやってみた。

stringからインスタンスを取得

完全に抽象化は無理で、具体レイヤーで必ず型アサーションをしなきゃいけない。

アサーションの例

a := new(A)

b, ok := reflect.ValueOf(a).Interface().(B)
// ok == false

ジェネリクスみたいなものがあれば、この限りではないと思うんだけど、Go言語にジェネリクスは無い

ということで以下参考とメモ

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()
    }
}

まあだいたい動くし、いっか

f:id:otiai10:20140616223429j:plain

というか型に型変数を渡して型に固定できないのならそれはジェネリクスとは言わないんじゃないかな...

まいっか。

DRYな備忘録