読者です 読者をやめる 読者になる 読者になる

Go で duck typing

string を返す String() を実装した型のオブジェクト foo があったとして、

fmt.Printf("%s", foo);

とすると foo.String() を評価してくれる。これが duck typing ってやつですね。特に何も宣言しないでも、とにかく String() が実装されていれば ok。

interface を使うと、引数にあるメソッドを持っている型のみ受け付けるようなことができる。

type Speaker interface {
	Speak() string;
}

Speaker という型は「string を返す Speak() を定義している」型の総称。

例えば Person と Dog という型を定義して、Speak() と String() を実装。

type Person struct {
	name string;
}
func NewPerson(name string) (self *Person) {
	self = new(Person);
	self.name = name;
	return;
}
func (self *Person) Speak() string {
	return fmt.Sprintf("私の名前は%sです", self.name);
}
func (self *Person) String() string {
	return self.name;
}

type Dog struct {
	name string;
}
func NewDog(name string) (self *Dog) {
	self = new(Dog);
	self.name = name;
	return;
}
func (self *Dog) Speak() string {
	return "ワンワン!";
}
func (self *Dog) String() string {
	return self.name;
}

Speaker 型を引数に取る say() を定義して、Person と Dog を渡してやる。

func say( speaker Speaker ) { 
	fmt.Printf("%s は言いました「%s\n", speaker, speaker.Speak() );
}
func main() {
	alice := NewPerson("Alice");
	bob   := NewDog("Bob");
	say(alice);
	say(bob);
}

実行結果はこうなる。

Alice は言いました「私の名前はAliceです」
Bob は言いました「ワンワン!」

Speak() を実装していない Fish を定義して、say() に渡すようなコードを書くと、コンパイルエラーになる。

type Fish struct {
	name string;
}
func NewFish(name string) (self *Fish) {
	self = new(Fish);
	self.name = name;
	return;
}
func (self *Fish) String() string {
	return self.name;
}

func main() {
	carol := NewFish("Carol");
	say(carol);
}
$ 8g duck.go
duck.go:60: *Fish is not Speaker
	missing Speak() (string)

「Fish は Speaker じゃない。Speaek() (string) が見つからないから」と。

ここまでで、特に "Person implements Speaker" みたいな宣言はしていないところが duck typing ということですね。

今回書いたソースコードはこちら http://gist.github.com/232758