ゼロから学ぶGo言語プログラミング(11) A Tour of Go 49~54
A Tour of Goの続き。 メソッドから。
49.Methods and Interfaces
オブジェクト指向というのでなく、「メソッドとインターフェース」となっている。 クラスベースではないということ?
50.Methods
いきなり答えが。
Goには、クラス( class )のしくみはありませんが、struct型にメソッド( method )を定義できます。
どうやらstructが重要らしい。
structはfieldをもったデータ構造、という漠然としたイメージ。 このシンプルな構造体に何らかの形で紐付けられた関数が、メソッドということだろうか。
メソッドレシーバ ( method receiver )は、 func キーワードとメソッド名の間で、それ自身の引数リストで表現します。 (訳注:この例では、 (v *Vertex) のことを言っている)
んー、このコードらしい。
func (v *Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) }
func の後、関数名との間に (v *Vertex) という見慣れない書き方。 これはその関数が、何らかのstructに属することを表現するんだろうか。 関数名の前に来る辺り、それを意図している気がする。 この部分のことを「メソッドレシーバ」と呼ぶらしい。
メソッドレシーバに書いてあるのは、vという値名と、*VertexというVertexへのポインタ。 これはどう理解すべきなのか。
ポインタの概念しか知らず、きっちり実用した経験がないため、メモリの番地としか認識していなかった。 でも調べてみると、どうもメモリの操作を直接するためというより、値の渡し方の制御のために、Goではポインタが採用されているらしい。
Goでは関数への値は常に参照ではなく値渡しになる。 とすると、もしメソッドレシーバにポインタを使わなければ、メソッドを使うたびにそのstruct自体がコピーされて、メソッドに渡るということだろうか。
Goでポインタが使われるのは、原則値渡しのGoで参照渡しを実現するため、というのがひとつの大きな理由にありそうだ。
51.Methods continued
なんと、structでなくてもメソッドを持てるらしい。
試してみる。
package main import ( "fmt" ) type Foo string func (f Foo) bar () { fmt.Println(f) } func main() { ff := Foo("asdfghjkl") ff.bar() }
実行すると"asdfghjkl"が出力された。 けど、 type Foo string ではなく単なる int にメソッドを割り当てようとすると、エラーが。
cannot define new methods on non-local type int
これが以下の記述で述べていることのようだ。
しかし、他のパッケージからの型や基本型にはメソッドを定義できません。
基本型だけでなく、他のパッケージで定義されている型も、例えエクスポートされていようがメソッドは定義できない。 確かに基本型や他のパッケージの型には触れない方が不要な上書きのトラブルを防げる。
さらに、このコードをポインタで渡すように変えてみると、今度は出力がおかしい。 メモリの番地を吐いているっぽい。
ところがこれを以下のように、structを用いると、正しく出力される。
package main import ( "fmt" ) type Foo struct { a float64 } func (f *Foo) bar () { fmt.Println(f.a) } func main() { ff := Foo{10} ff.bar() }
intやstringで型を宣言して、それをポインタ渡しでメソッドに関連付け、メソッドから型を参照すると、それは当然ポインタ。 出力すればポインタの番地が返ってくる。 ということは、structが特殊なのか。 色々読んでみる。
そうか、ポインタは var という風に、付きでアクセスすると、そのポインタが示す変数への参照となる。 メソッドレシーバで渡されたintのポインタが示す値を使うには、*が必要だった。 そしてstructの場合、. がそれに似た役割を果たしている、と。
わかったようなわかっていないような。
52.Methods with pointer receivers
メソッドは、名前付きの型( named type )や、名前付きの型へのポインタに関連付けられます。
名前付きの型とは、typeキーワードで宣言するもののことだろう。 メソッドはそうやって宣言した型そのものか、そのポインタに関連付けることが出来る。
解説にしたがってポインタへの参照に書き換えると、確かにScaleメソッドでの変更が反映されなくなった。
メソッドを型に関連付ける際、実体で渡せば値渡しとなり、メモリの消費が増え、変更の直接反映ができない。 ポインタで渡せば参照渡しとなり、メモリ消費を抑え、変更も直接反映される。
なんか色々わかった気がする。
53.Interfaces
インターフェースも、これまでの経験でしっかり扱ってこなかったもの。 満たすべき実装を示しているとか、ポリモーフィズムに使えるとか、概要程度は理解していても活用はしていない。
インターフェース型( interface type )は、メソッド群で定義されます。
というのが以下のことだろう。
type Abser interface { Abs() float64 }
structを使って型を宣言する場合と同じようだけど、structの代わりにinterface。 そして中身は関数名と戻り値の型?
コード全体は interface に慣れていないため追いかけにくい。 a = v で、aがAbserインターフェースとなり、そこにMyFloatやVertexを代入しようとして、Vertexの方ではエラー。 ここまでは解説やコメントにある通りなのでわかる。 しかしなぜ、Vertexの方のメソッドレシーバが (v *Vertex) でポインタ渡しだと、エラーになるのか。
ポインタレシーバだからエラーだということは、インターフェース時点で、これが値渡しで実装されるよう定義されているはずなのに、それがどの箇所かわからない。
interfaceはGoで重要なはずだけれど、ここだけ見ていても進みそうにないので、次へ。
54.Interfaces are satisfied implicitly
言ってることはなんとなくわかる。
インターフェースという多態性を担保する仕組みがあり、Goではメソッドの実装そのものが即ちインターフェースの実装であって、別途宣言は要らない。
しかし、なんとなく止まり。
インターフェースは非常に重要そうなわりに、このツアーではこれで終わり。 どこかでみっちり理解できるよう取り組まないと。