言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

Go For C++ Programmers #interface の勝手に和訳

普段はグーグルとかそういうトレンディな場所には近づかないようにしているのですが、継承最悪説、継承こそがオブジェクト指向の生み出した悪の根源だと唱えたり唱えなかったりしている自分としては Go For C++ Programmers #interfaces のようなドキュメントをみるとうれしくなってしまいまして、Go For C++ Programmers の真ん中くらいの "Interfaces" の部分を勝手に和訳しました。権利的に問題があれば削除します。

何となくたかはらさんの Go For C++ Programmersの勝手に和訳 に文体を合わせました。

以下、訳となります。

C++ でクラスやサブクラス、テンプレートがあるけど、Go ではインタフェースがあるよ。Go のインタフェースは C++ の純粋抽象関数に似ている。これってデータメンバが無くて純粋抽象メソッドしか無いクラスの事だね。でも Go では、インタフェースにあるメソッドを提供すればどんな型でもそのインタフェースの実装にしてくれるよ。はっきり継承を宣言しなくても良いんだ。インタフェースの実装はインタフェースそのものとは全然関係ないよ。

メソッドは普通の関数定義っぽい、レシーバがある以外はね。レシーバは C++ の this と似ています。

type myType struct { i int }
func (p *myType) get() int { return p.i }

これが myType に関連したメソッド get の定義です。レシーバは関数定義の中では p と呼ぶよ。

メソッドは型の名前を使って定義するよ。もしもこの値を他の型に変更したい時は、もとの型じゃ無くて新しい方にメソッドを作ればいいよ。

組み込み型にメソッドを定義したい時は組み込み型から由来した新しい型を作ればいいよ。新しい方は組み込み型とは区別される。

type myInteger int
func (p myInteger) get() int { return int(p) } // 変換が要るよ
func f(i int) { }
var v myInteger
// f(v) はまちがい。
// f(int(v)) は大丈夫。 ; int(v) にはメソッドが定義されていないよ。

こんなインタフェースがあったら

type myInterface interface {
	get() int;
	set(i int);
}

次を足せば myType がこのインタフェースを満たすよ。

func (p *myType) set(i int) { p.i = i }

すると myInterface を受け取る関数ならなんでも *myType 型の変数を受け取ってくれるよ。

func getAndSet(x myInterface) {}
func f1() {
	var p myType;
	getAndSet(&p);
}

いいかえると、もしも myInterface を C++ の純粋抽象関数だとするでしょ、*myType の set と get が定義されてあれば *myType は勝手に myInterface を継承したって事になるんだ。一つの型が複数のインタフェースを満たしていても良いよ。

匿名フィールドを使って C++ の子クラスっぽい事も出来る。

type myChildType struct { myType; j int } // 訳注: 最初のフィールドに型名だけあって変数名が省略されてます。
func (p *myChildType) get() int { p.j++; return p.myType.get() }

これは myChildType を myType の子クラスとして実装したのとだいたい同じ。

func f2() {
	var p myChildType;
	getAndSet(&p)
}

ここで、set メソッドは事実上 myChildType に継承されるよ。というのも匿名フィールドに定義されているメソッドは囲まれた型のメソッドに格上げされるからです。この場合、myChildType が myType 型の匿名フィールドを持つので、myType のメソッドが myChildType のメソッドになるわけね。この例では get メソッドがオーバーライドされて set メソッドが継承されているね。

これはぴったり C++ の子クラスと同じわけではないよ。匿名フィールドのメソッドが呼ばれたとき、レシーバはそのフィールドで、囲んでいる構造ではない。別の言い方をすると、匿名フィールドのメソッドは仮想関数ではないです。仮想関数みたいな事がしたい時はインタフェースを使ってね。

インタフェース型の変数は、型表明という特別な仕組みを使って別のインタフェース型に変換できるよ。これは実行時に動的に働く。C++ の dymanic_cast みたいだね。dymanic_cast と違って、二つのインタフェースの間に宣言的な関係は要らないです。

type myPrintInterface interface {
  print();
}
func f3(x myInterface) {
	x.(myPrintInterface).print()  // myPrintInterface への型表明
}

この myPrintInterface への変換は完璧に動的です。この変換は使われている x の型 (動的型) が print メソッドを実装していたらちゃんと動くよ。

変換は動的だから、C++ のテンプレートみたいなジェネリックプログラミングに使えるかも。これは最低限のインタフェースを使って値の操作で可能です。

type Any interface { }

コンテナは Any を使って書けるよ。でも呼び出し側で型表明を使って、中身の型の値に戻さないといけないです。型付けは静的じゃなくて動的なので、C++ がテンプレートをインラインにするような方法は無いよ。操作は実行時に完全に型検査されるけど、全部の操作は関数呼び出しに関係するよ。

type iterator interface {
	get() Any;
	set(v Any);
	increment();
	equal(arg *iterator) bool;
}

以上:

Smalltalk 記法抜きの Objective-C ってところでしょうか。