Go言語の関数とメソッドについてです。
前半はおさらい的な内容で、後半でちょっとしたネタを紹介します。
関数
まず普通の関数です。func hello() { fmt.Println("Hello, Gophers") }
関数を変数に代入
関数を変数に代入することも出来ます。f := hello f(); // "Hello, Gophers"
匿名関数
関数リテラルで匿名関数を定義することが出来ます。f := func() { fmt.Println("Hello, Gophers") } f(); // Hello, Gophers
メソッド
レシーバーと関連付けた関数をメソッドと言います。type Test struct { Name string } func (t Test) hello() { fmt.Printf("Hello, %s\n", t.Name) } func main() { t := Test{Name: "Gophers"} t.hello() // "Hello, Gophers" }
メソッドを変数に代入
メソッドも関数と同じように変数に代入することができます。t := Test{Name: "Gophers"} f := t.hello f(); // "Hello, Gophers"このとき、レシーバーの値はメソッドと関連付けて保持されます。
レシーバーの型
このあたりから本番です。メソッドを変数に代入したあとに元の値を書き換えると……
t := Test{Name: "Gophers"} f := t.hello f(); // "Hello, Gophers" t.Name = "Go Rangers" f(); // "Hello, Gophers" になる。"Hello, Go Rangers" ではない!おや、"Hello, Go Rangers" にならずに "Hello, Gophers" となりました。
これは`hello`メソッドのレシーバーがポインタではなく値だからです。
変数`f`のレシーバーは、メソッドを変数に代入した時にコピーされており、`Name`の中身はGophersになっています。その後で行われた変数`t`への操作の影響は受けません。
レシーバーの型をポインタにしてみると
func (t *Test) helloPtr() { fmt.Printf("Hello, %s\n", t.Name) }
f := t.helloPtr f(); // "Hello, Gophers" t.Name = "Go Rangers" f(); // "Hello, Go Rangers"メソッドを変数に代入したあとの、Nameへの変更が反映されています。変数`f`にバインドされているのはポインタになっているからです。
レシーバーの型が値なのかポインタなのかは、メソッド内でレシーバーの中身を書き換える場合にも関係しますので、意識しておきましょう。
nilに対してメソッドを呼び出す
さて、レシーバーがポインタの場合にnilに対してメソッドを呼び出してみましょう。以下の例だと変数`c`はnilです。その変数`c`からメソッド`hello`を呼び出します。
type Cat struct { Name string } func (c *Cat) hello() { fmt.Println("myaa") } func main() { var c *Cat // c は nil c.hello() // myaa }ぬるぽ……にならず、この例だとpanicも起きずに動作します。
なぜならば、nilポインタとなっている変数`c`の中身が使われないので、不正なアドレスへのアクセスが起きないためです。
変数`c`の中身はnilですが、変数`c`自体は実態を持っていますのでメソッドが呼び出せるわけです。
ちなみに変数`c`の中身を使おうとするとpanicします。
func (c *Cat) hello2() { fmt.Printf("myaa, %s\n", c.Name) // panic: runtime error: invalid memory address or nil pointer dereference }これはnilのアドレスを解決しようとしているので、当然の動作です。
メソッドに違う方法でアクセスしてみる
さて、また違った例を見てみましょう。type Sample struct { Name string } func (s Sample) hello() { fmt.Printf("Hi %s\n", s.Name) } func main() { s := Sample{"Gophers"} Sample.hello(s) // Hi Gophers }変数`s`を通してではなく、`Sample.hello`という関数名を通してメソッド呼び出しています。
`Sample.hello`の型は`func(Sample)`となっていて、第一引数に`Sample`を受け取るので、上記の例のように呼び出すこともできます。
レシーバーがポインタの場合は以下のようになります。
func (s Sample) helloPtr() { fmt.Printf("Hi %s\n", s.Name) }
sp := &Sample{"Gophers"} (*Sample).helloPtr(sp) // Hi Gophersこちらもレシーバー型とメソッド名から、メソッドを取得できています。`(*Sample).helloPtr`の型は`func(*Sample)`です。
もちろん、変数に代入することも出来ます。
fv := Sample.hello fp := (*Sample).helloPtr
処理の対象となるオブジェクトが定まっていないけれど、メソッドを変数に入れておいて使いたい、という時に使えそうです。
interfaceでも同様のコードを書くことが出来ます。
type Duck struct {} type Sounder interface { Sound() } func (d *Duck) Sound() { fmt.Println("quack") } func main() { var f func(Sounder) = Sounder.Sound d := &Duck{} f(d) // quack }
感想など
メソッドの型は、第一引数に構造体を受け取る関数と同じになっています。変数を通してメソッドを呼び出せますが、ある種のシンタックスシュガーと言えるかもしれません。もちろんinterfaceの定義を満たしているかどうかによるサブタイピングがあるので、メソッドを定義する意味はありますし、使わない手はありません。
以上、ちょっとしたネタでしたが、知っておくといつか役に立つと思います。
参考
- http://golang.org/ref/spec#Function_literals- http://golang.org/ref/spec#Method_expressions
- http://golang.org/ref/spec#Method_values
0 件のコメント:
コメントを投稿