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 件のコメント:
コメントを投稿