マスター・オブ・
reflectパッケージ II
2016/04/17(日)
@第3回 関西golang勉強会
The Go gopher was designed by Renee French.
The gopher stickers was made by Takuya Ueda.
Licensed under the Creative Commons 3.0
Attributions license.
参考:マスター・オブ・reflectパッケージ
Go研 Vol.8
アジェンダ
■ 自己紹介
■ 基礎編
● reflectパッケージとは?
● Value型とType型
● 変数に値を入れる
● 構造体を触る
■ 応用編
● 任意の型にメソッドを生やしたい
● 任意個のチャネルに対してselectしたい
● 実行時に任意の型を作りたい
■ まとめ
2
自己紹介
KLab株式会社
KLabGames事業本部 エンジニア
@六本木
上田拓也
twitter: @tenntenn
■ 好きな言語
Go, JavaScript, Lua
■ 業務
モバイルオンラインゲームの開発:Unity, Lua
■ 最近はまってること
英会話
3
基礎編
5
reflectパッケージとは?
■ 何ができるのか?
● 実行時に型情報を取得
● 任意の型の変数に値を入れる
● 構造体のフィールドのタグを取得する
■ どこで使われてるの?
● encodingパッケージ
● ORマッパーなど
■ なんで使うの?
● ジェネリクスがない
● ただ、ただ楽しい!!!
基礎編
6
Value型とType型
■ Value型
● 任意の値を表す型
● 値への操作をメソッドで提供
● reflect.ValueOf()で取得できる
■ Type型
● 任意の型を表す型
● 型に関する操作をメソッドで提供
● reflect.TypeOf()で取得できる
基礎編
7
変数に値を入れる
基礎編
8
var n int
fmt.Println(n) // 0
vp := reflect.ValueOf(&n)
v := vp.Elem()
if v.CanSet() {
v.SetInt(100)
}
fmt.Println(n) // 100
http://play.golang.org/p/HkJPjQsP8o
構造体を触る
基礎編
9
s := struct{
A string `k:"v"`; b int
}{"a", 1}
v := reflect.ValueOf(&s).Elem() println(v.
FieldByName("A").CanSet())
println(v.FieldByName("b").CanSet())
f1, ok := v.Type().FieldByName("A")
println(ok, f1.PkgPath, f1.Tag.Get("k"))
f2, _ := v.Type().FieldByName("b")
println(f2.PkgPath)
http://play.golang.org/p/NkwP3KSjDu
応用編
10
任意の型にメソッドを生やしたい 1
■ メソッド
● パッケージ内の型をレシーバにできる
● メソッド値として扱える
応用編
11
type MyInt int
func (n MyInt) Int() int {
return int(n)
}
func main() {
f := MyInt(100).Int
println(f())
}
http://play.golang.org/p/b4p60PpJtj
任意の型にメソッドを生やしたい 2
■ reflectで関数を作る
● MakeFuncを使う
応用編
12
func Compose(f, g, fptr interface{}) {
fv, gv := reflect.ValueOf(f), reflect.ValueOf(g)
fgv := func(in []reflect.Value) []reflect.Value {
return gv.Call(fv.Call(in))
}
out := reflect.ValueOf(fptr).Elem()
v := reflect.MakeFunc(out.Type(), fgv)
out.Set(v)
}
http://play.golang.org/p/_bNy3H2575
任意の型にメソッドを生やしたい 3
■ メソッドのValue値へ値を設定できるか?
● できない!
応用編
13
n := MyInt(100)
v := reflect.ValueOf(&n).Elem()
fv := v.MethodByName("Int")
// false
println(fv.CanSet())
http://play.golang.org/p/yXFpnucbPw
任意の型にメソッドを生やしたい 4
■ メソッドの振る舞いを動的に変える
● インタフェースを埋め込む
応用編
14
type Hoge interface{M()}
type fuga struct {Hoge}
func (f fuga) M() {
fmt.Println("Hi")
f.Hoge.M() // 元のメソッドを呼ぶ
}
func HiHoge(h Hoge) Hoge {
return fuga{h} // 構造体作る
}
Mの振る舞いを変える
任意個のチャネルに対してselectしたい 1
■ select
● 複数チャネルの受信/送信を行う
● コンパイル時に対象のチャネル数は決定
応用編
15
select {
case n := <-ch1:
println(n)
case ch2 <- 100:
default:
println("Defualt")
}
任意個のチャネルに対してselectしたい 2
■ reflectでselectする
● reflect.Selectを使う
● 任意の個数のSelectCaseを渡せる
応用編
16
ch1, ch2 := make(chan int), make(chan int)
go func() {ch1 <- 100}()
go func() {<-ch2}()
i, v, ok := reflect.Select([]reflect.SelectCase{
// case: <-ch1
{reflect.SelectRecv, reflect.ValueOf(ch1), reflect.ValueOf(nil)},
// case: ch2 <- 100
{reflect.SelectSend, reflect.ValueOf(ch2), reflect.ValueOf(100)},
// default:
{reflect.SelectDefault, reflect.ValueOf(nil), reflect.ValueOf(nil)},
})
fmt.Println(i, v, ok)
http://play.golang.org/p/7t3awR7EZC
実行時に任意の型を作りたい 1
■ Type型
● 実はインタフェース
● reflect.New(typ Type) Value
○ 任意の型の値を生成できる
応用編
17
type MyType struct {reflect.Type}
func (typ MyType) Kind() reflect.Kind {
return reflect.Bool
}
t:= MyType{reflect.TypeOf(0)}
println(t.Kind()) // bool
v := reflect.New(t)
Kindの挙動だけ変える
実行時に任意の型を作りたい 2
■ そんなに現実は甘くない
応用編
18
$ go run main.go
bool
panic: interface conversion: reflect.Type is
main.MyType, not *reflect.rtype
エクスポートされてない!
実行時に任意の型を作りたい 3
■ Type型の実態
● *rtype型がType型を実装している
■ New関数の実装
応用編
19
func New(typ Type) Value {
if typ == nil {
panic("reflect: New(nil)")
}
ptr := unsafe_New(typ.(*rtype))
fl := flag(Ptr)
return Value{typ.common().ptrTo(), ptr, fl}
}
ここでpanicが起きる
まとめ
■ reflectパッケージは楽しくて強力
● 動的に色々できる
○ StructTagの取得
○ 任意個のSelectCase
■ 使うときは慎重に
● reflectしかできないこと?
● 自動コード生成でどうにかならない?
● インタフェースで十分では?
● パフォーマンスは?
20
マスター・オブ・reflectパッケージ II

マスター・オブ・reflectパッケージ II