This page looks best with JavaScript enabled

Goのreflectパッケージで色々作ってみる

 ·  ☕ 6 min read  ·  ✍️ reud · 👀... views

面白いけど難しい!でも型で遊ぶのはやっぱり楽しい!

Goのreflectパッケージに用いることで、抽象的に型を扱うことが出来るようになり、表現の幅が広がるのですが、まぁ理解が難しいので
練習としてこれを用いて色々なメソッドを作ってみたいと思います。

検証環境はgo version go1.15.6 linux/amd64です。

型の情報を取得

  • Typeメソッドで出来ます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import (
	"fmt"
	"reflect"
)

func main() {
	//型の名前を取得出来る。
	fmt.Printf("type: %+v\n",reflect.TypeOf("x")) // => type: string
}

表示される、intは何の型でしょうか。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import (
	"fmt"
	"reflect"
)

func main() {
	//型の名前を取得出来る。
	fmt.Printf("type: %+v\n",reflect.TypeOf(reflect.TypeOf("x"))) //  => type: *reflect.rtype
}

パット見同じに見えるメソッドにKindというものがあります。

https://golang.org/pkg/reflect/#Kind に書いてあることは

A Kind represents the specific kind of type that a Type represents. The zero Kind is not a valid kind.

ですが、ちょいと英文が理解できずなのでその下に書かれたKindの定義を見たほうがわかりやすいかと。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)

この中に対応するものを返すということです。
返す型から変わってきますが、さらに明らかに違うものであることを確認するために以下のソースコードについて見ていきます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"fmt"
	"reflect"
)

type stct struct {
}

func main() {
	//ケース1
	type MyInt int
	var x MyInt
	x = 3 // Type: MyInt
	y := 4 // Type: int
	fmt.Printf("Type() check: ")
	fmt.Printf("x: %+v y: %+v\n",reflect.TypeOf(x).String(), reflect.TypeOf(y).String()) // => x: main.MyInt y: int

	fmt.Printf("Type() check: ")
	fmt.Printf("x: %+v y: %+v\n",reflect.ValueOf(x).Kind(), reflect.ValueOf(y).Kind()) // => x: int y: int
}

型によって動作を分けたい場合はKindの方を使うと良いでしょう。

reflect.TypeOfreflect.ValueOfのち外はこちらに

色々作ってみる

いい感じに二つを足し合わせる関数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import (
	"fmt"
	"reflect"
	"strconv"
)

// x + y を返す。 x,yはそれぞれint or stringでxのインターフェースを返す。(64bitで実行する想定で・・・)
func softAdd(x,y interface{}) interface{} {
	switch vx := reflect.ValueOf(x); vx.Kind() {
	case reflect.Int:
		switch vy := reflect.ValueOf(y); vy.Kind() {
		case reflect.Int:
			return int(vx.Int()+vy.Int())
		case reflect.String:
			a, _ := strconv.ParseInt(vy.String(),10,64)
			return int(vx.Int()+a)
		}
	case reflect.String:
		switch vy := reflect.ValueOf(y); vy.Kind() {
		case reflect.Int:
			return vx.String()+strconv.Itoa(int(vy.Int()))
		case reflect.String:
			return vx.String()+vy.String()
			}
	}
	return nil
}

func main() {
	ix := 1
	sx := "2"

	iy := 3
	sy := "4"

	r := func(v ...interface{}) {
		fmt.Printf("result: %+v\n",v...)
	}

	r(softAdd(ix,iy)) // => 4
	r(softAdd(ix,sy)) // => 5
	r(softAdd(sx,iy)) // => "23"
	r(softAdd(sx,sy)) // => "24"
}

これをさらに拡張したい。具体的にはポインタも許可したい。

ポインタ型と値型を区別無く操作したい場合はIndirectというメソッドがある。

https://golang.org/pkg/reflect/#Indirect

変数をreflect.Valueofしたものを引数にしてあげると、それがポインタだったら指した先をreflect.Value型にして返してくれる。(ポインタじゃない場合はそのまま動く)

なんとなくソースを覗いてみたらすごくシンプル

https://golang.org/src/reflect/value.go?s=69954:69982#L2328

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Indirect returns the value that v points to.
// If v is a nil pointer, Indirect returns a zero Value.
// If v is not a pointer, Indirect returns v.
func Indirect(v Value) Value {
	if v.Kind() != Ptr {
		return v
	}
	return v.Elem()
}

早速これらを使って改良してみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package main

import (
	"fmt"
	"reflect"
	"strconv"
)

// x + y を返す。 x,yはそれぞれint or stringでxのインターフェースを返す。(64bitで実行する想定で・・・)
// ポインタ型が入力された場合は値型で返す。
func softAdd(x,y interface{}) interface{} {

	switch vx := reflect.Indirect(reflect.ValueOf(x)); vx.Kind() {
	case reflect.Int:
		switch vy := reflect.Indirect(reflect.ValueOf(y)); vy.Kind() {
		case reflect.Int:
			return int(vx.Int()+vy.Int())
		case reflect.String:
			a, _ := strconv.ParseInt(vy.String(),10,64)
			return int(vx.Int()+a)
		}
	case reflect.String:
		switch vy := reflect.Indirect(reflect.ValueOf(y)); vy.Kind() {
		case reflect.Int:
			return vx.String()+strconv.Itoa(int(vy.Int()))
		case reflect.String:
			return vx.String()+vy.String()
			}
	}
	return nil
}

func main() {

	// intかstringが来たら、それぞれ *int, *stringに変換する。
	toPtr := func(v interface{}) interface{} {
		switch rv := reflect.ValueOf(v); rv.Kind() {
		case reflect.Int:
			x := int(rv.Int())
			return &x
		case reflect.String:
			x := rv.String()
			return &x
		default:
			return v
		}
	}

	ix := 1
	sx := "2"

	iy := 3
	sy := "4"

	pix := toPtr(5)
	psx := toPtr("6")

	piy := toPtr(7)
	psy := toPtr("8")

	r := func(v ...interface{}) {
		fmt.Printf("result: %+v\n",v...)
	}

	r(softAdd(ix,iy)) // => 4
	r(softAdd(ix,sy)) // => 5
	r(softAdd(sx,iy)) // => "23"
	r(softAdd(sx,sy)) // => "24"

	r(softAdd(pix,piy)) // => 12
	r(softAdd(pix,psy)) // => 13
	r(softAdd(psx,piy)) // => "67"
	r(softAdd(psx,psy)) // => "68"
	r(softAdd(ix,psy)) // => 9
	r(softAdd(pix,sy)) // => 9
}

構造体のポインタが与えられたら再帰的にフィールドを見てint or *int or *string or stringなら変更を加える

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package main

import (
	"fmt"
	"reflect"
)

// intかstringが来たら、それぞれ *int, *stringに変換する。
func toPtr(v interface{}) interface{} {
	switch rv := reflect.ValueOf(v); rv.Kind() {
	case reflect.Int:
		x := int(rv.Int())
		return &x
	case reflect.String:
		x := rv.String()
		return &x
	default:
		return v
	}
}

// いい感じに7を足すメソッド。stringなら"7"が足されるし、Intなら7が足される。
// struct,int,stringのポインタに対し動作する。
// structの場合はフィールドに対して再帰的に動作する。(フィールドがSliceでもOK)
func recAddSeven(v interface{})  {

	var recFunc func(v interface{})
	// すべての引数は参照型orポインタになるようにしている。
	recFunc = func(v interface{}) {
		switch rv := reflect.Indirect(reflect.ValueOf(v)); rv.Kind() {
		// *int
		case reflect.Int:
			rv.SetInt(rv.Int()+7)
			fmt.Printf("set val: %+v\n",v)
		// *string
		case reflect.String:
			rv.SetString(rv.String()+"7")
			fmt.Printf("set val: %+v\n",v)
		case reflect.Struct:
			for i := 0; i < rv.NumField(); i++ {
				fieldTag := rv.Type().Field(i)
				fieldValue := reflect.Indirect(rv.FieldByName(fieldTag.Name))
				fmt.Printf("types: %+v\n",fieldValue.Kind().String())
				// アドレスのポインタになるので、 *string, *int, *struct{} が引数の型になる。
				switch fieldValue.Kind() {
				case reflect.String,reflect.Int,reflect.Struct,reflect.Slice:
					recFunc(fieldValue.Addr().Interface())
				}
			}
		case reflect.Slice:
			for i := 0; i < rv.Len(); i++ {
				e := rv.Index(i)
				recFunc(e.Addr().Interface())
			}
		}
		return
	}

	// ポインタが渡された場合には処理を実行する。(*string,*int,*struct想定)
	switch rv := reflect.ValueOf(v); rv.Kind() {
	case reflect.Ptr:
		recFunc(v)
	default:
		return
	}
}

func main() {


	// フィールドに自身の構造体ポインタを持つ構造体の場合
	type S struct {
		Str string
		Ingr int
		Hoge *string
		S *S
	}
	s := &S{
		S: &S{
			S: &S{
				S: &S{
					Hoge: toPtr("four").(*string),
					Str: "Four",
					Ingr: 4,
				},
				Hoge: toPtr("three").(*string),
				Str: "Three",
				Ingr: 3,
			},
			Hoge: toPtr("two").(*string),
			Str: "Two",
			Ingr: 2,
		},
		Hoge: toPtr("one").(*string),
		Str: "One",
		Ingr: 1,
	}

	r := func(v ...interface{}) {
		fmt.Printf("result: %+v\n",v...)
	}

	r(*s)
    recAddSeven(s)
    // 良い感じにするメソッドが必要だな!ガッハッハ
	r((*s).Str)
	r((*s).S.Str)
	r((*s).S.S.Str)
	r((*s).S.S.S.Str)
	r((*s).Ingr)
	r((*s).S.Ingr)
	r((*s).S.S.Ingr)
	r((*s).S.S.S.Ingr)
	r(*(*s).Hoge)
	r(*(*s).S.Hoge)
	r(*(*s).S.S.Hoge)
	r(*(*s).S.S.S.Hoge)

	type S2 struct {
		Slice []string
	}

	// スライスを持つ構造体
	s2 := &S2{Slice: []string{"1","2","3"}}
	recAddSeven(s2)
	r(s2)


}

結果: https://play.golang.org/p/Nn17lhCIG7Z

色々調べているうちに出た謎

これは誰か教えて欲しいんですが、

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"fmt"
)

func main() {
	v := 7
	f := func(x interface{}) {
		k := 8
		x = &k
	}
	f(&v)
	fmt.Printf("%+v",v) // => 7
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
	"fmt"
)

func main() {
	v := 7
	f := func(x *int) {
		k := 8
		x = &k
	}
	f(&v)
	fmt.Printf("%+v",v) // => 7
}

これどっちも8じゃないんですか・・・?(kの指すポインタの先が8だから8)が出力されるつもりなんですが・・・

というか引数がinterface{}型のメソッドにおいて参照渡しが出来るパターンが分からん!(reflect必須なんですかね・・・?)

参考

Share on

reud
WRITTEN BY
reud
Stundent