🦆

TypeScriptでインターフェースを使ってGoっぽくダックタイピングやりたい

2024/02/07に公開

はじめに

かろっくです。
今回は個人的なメモも兼ねたゆるい記事です。

TypeScript でインターフェースを使って、Go っぽくダックタイピングをやってみようと思います。
まだまだ勉強中なので、間違いがあるかもしれませんが、優しくご指摘いただけると幸いです。

ダックタイピングとは

Wikipedia を引用します。
https://ja.wikipedia.org/wiki/ダック・タイピング

オブジェクトがあるインタフェースのすべてのメソッドを持っているならば、たとえそのクラスがそのインタフェースを宣言的に実装していなくとも、オブジェクトはそのインタフェースを実行時に実装しているとみなせる、ということ

ここで、インターフェースとは、オブジェクトの持つメソッドの集合を定義するものです。

インターフェースが関数の集合を定義し、その関数をすべて持っているオブジェクトは、そのインターフェースを実装しているとみなせる、ということです。

実際に 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)という仕組みによるものです。

https://typescriptbook.jp/reference/values-types-variables/structural-subtyping

その型の構造が一致していれば、その型は同じとみなされます。

これをインターフェースにおいて利用することで、Go と同じようなダックタイピングを実現することができています。

おわりに

ゆるくブログを書いてみました。

ここ二日くらい、Go や Rust でのインターフェース、トレイト等を調べております。
頭がすっきりしたら、また何か記事にしたいと思います。

GitHubで編集を提案

Discussion