ゼロから学ぶGo言語プログラミング(11) A Tour of Go 49~54

f:id:belbo:20140616112930j:plain

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ではメソッドの実装そのものが即ちインターフェースの実装であって、別途宣言は要らない。

しかし、なんとなく止まり。

インターフェースは非常に重要そうなわりに、このツアーではこれで終わり。 どこかでみっちり理解できるよう取り組まないと。