TypeScriptでインターフェースを使ってGoっぽくダックタイピングやりたい
はじめに
かろっくです。
今回は個人的なメモも兼ねたゆるい記事です。
TypeScript でインターフェースを使って、Go っぽくダックタイピングをやってみようと思います。
まだまだ勉強中なので、間違いがあるかもしれませんが、優しくご指摘いただけると幸いです。
ダックタイピングとは
Wikipedia を引用します。
オブジェクトがあるインタフェースのすべてのメソッドを持っているならば、たとえそのクラスがそのインタフェースを宣言的に実装していなくとも、オブジェクトはそのインタフェースを実行時に実装しているとみなせる、ということ
ここで、インターフェースとは、オブジェクトの持つメソッドの集合を定義するものです。
インターフェースが関数の集合を定義し、その関数をすべて持っているオブジェクトは、そのインターフェースを実装しているとみなせる、ということです。
実際に Go でのダックタイピングの例を見てみましょう。
まず、以下のようなインターフェースを定義します。
sound メソッドを持つ Animal インターフェースです。
type Animal interface {
sound() string
}
次に、以下のような Dog 構造体を定義します。
type Dog struct {
Name string
}
Dog 構造体に対応する sound メソッドを定義します。
func (d Dog) sound() string {
return "わんわん"
}
そうすることで、Dog 構造体は Animal インターフェースを実装しているとみなされ、Animal インターフェースを引数に取る関数に Dog 構造体を渡すことができます。
func makeSound(a Animal) {
fmt.Println(a.sound())
}
makeSound 関数は、Animal インターフェースを引数に取り、それの sound メソッドを呼び出します。
func main() {
dog := Dog{Name: "ポチ"}
makeSound(dog) // わんわん
}
ここで、Dog 構造体が Animal インターフェースを暗黙的に実装しているということがわかります。
では実験として、sound メソッドを持たない構造体を定義してみます。
type Cat struct {
Name string
}
func main() {
cat := Cat{Name: "たま"}
makeSound(cat) // エラー
}
./prog.go:25:12: cannot use dog (variable of type Dog) as Animal value in argument to makeSound: Dog does not implement Animal (missing method sound)
TypeScript でのダックタイピング
では、TypeScript で同じようなことをやってみましょう。
まず、以下のようなインターフェースを定義します。
interface Animal {
sound(): string;
}
次に、以下のような Dog クラスを定義します。
このクラスの中で、sound メソッドを定義しています。
class Dog {
constructor(public name: string) {}
sound(): string {
return "わんわん";
}
}
では、Animal インターフェースを引数に取る関数を定義してみます。
function makeSound(a: Animal) {
console.log(a.sound());
}
ここで、TypeScript の仕様により、Dog クラスは Animal インターフェースを実装しているとみなされます。
const dog = new Dog("ポチ");
makeSound(dog); // わんわん
では実験として、sound メソッドを持たないクラスを定義してみます。
class Cat {
constructor(public name: string) {}
}
このクラスは sound メソッドを持っていないため、Animal インターフェースを実装していないとみなされます。
const cat = new Cat("たま");
makeSound(cat); // エラー
Argument of type 'Dog' is not assignable to parameter of type 'Animal'. Property 'sound' is missing in type 'Dog' but required in type 'Animal'.ts(2345) test.ts(2, 3): 'sound' is declared here.
このような動作を TypeScript が行うのは、構造的部分型(structural subtyping)という仕組みによるものです。
その型の構造が一致していれば、その型は同じとみなされます。
これをインターフェースにおいて利用することで、Go と同じようなダックタイピングを実現することができています。
おわりに
ゆるくブログを書いてみました。
ここ二日くらい、Go や Rust でのインターフェース、トレイト等を調べております。
頭がすっきりしたら、また何か記事にしたいと思います。
Discussion