Masayan tech blog.

  1. ブログ記事一覧>
  2. TypeScriptの構造的部分型をアニメ「NARUTO」で説明する

TypeScriptの構造的部分型をアニメ「NARUTO」で説明する

公開日

環境

  • macOS Monterey 12.6
  • Windows 11
  • VSCode
  • node.js v18.6.0
  • npm 8.13.2
  • Typescript 4.6.4

公称型と構造的部分型

いずれも、型の互換性をどのように判断するかを表す概念です

内容

言語

公称型

それぞれに継承関係があれば互換性があるとみなす

Java, PHP

構造的部分型

それぞれの構造をチェックし、それが一致すれば両者には互換性があるとみなす

TypeScript, Go

具体例

以下の通り、うちはイタチとサスケはうちは一族なので、うちは一族クラスを継承し、写輪眼をデフォルトで使用することできます

class UchihaClan {
  public sharingan() {
    console.log('sharingan!!');
  }
}

class Itachi extends UchihaClan {
  public tsukuyomi() {
    console.log('tsukuyomi!!');
  }
}

class Sasuke extends UchihaClan {
  public kagutsuchi() {
    console.log('kagutsuchi!!');
  }
}

写輪眼に加えて、イタチは月読を、サスケはカグツチを使うことができる

let uchihaClan = new UchihaClan();

const itachi = new Itachi();
const sasuke = new Sasuke();

itachi.tsukuyomi();
sasuke.kagutsuchi();

また、うちは一族クラスとうちはイタチ/サスケは継承関係にあるため、以下のように代入もできます

uchihaClan = itachi;
uchihaClan = sasuke;

以上は前置きで、ここからが本題ですが、ここに、はたけカカシが登場しました。カカシはうちは一族ではないですが、写輪眼を使用することができます(※理由は割愛)

class Kakashi {
  public sharingan() {
    console.log('sharingan!!');
  }
}

カカシクラスは、見た目がうちは一族クラスと似ています。

構造的部分型では、それぞれの構造をチェックし、それが一致すれば両者には互換性があるとみなすため、以下のように、うちは一族にカカシを代入することができます

const kakashi = new Kakashi();
uchihaClan = kakashi; //OK

理由は、Kakashiクラスが、UchihaClanクラスと同様に、sharinganという同じ名称のメソッドを持っており、文字通り構造が同じであると判断されて、代入することが可能になるためです

一方で、公称型の言語ではオブジェクト同士の継承関係により判定されるため、上記のような代入は不可となっています

互換性がないとみなされるケース

上記の前提を踏まえたうえで、互換性がないとみなされるパターンを2種類ほど上げます

代入される側にprivate・protectedメソッドやフィールドがある

代入される側にprivate・protectedメソッドやフィールドがある(TypeScriptでは、クラスに1つでも非パブリックなプロパティがあると、構造的部分型ではなく公称型になる)

※以下のように、代入する側に同名のprivate・protectedメソッドがあっても、互換性がなくなります

classUchihaClan {
  private sharingan() {
    console.log('sharingan!!');
  }
}

class Kakashi {
  private sharingan() {
    console.log('sharingan!!');
  }
}

let uchihaClan = new UchihaClan();
const kakashi = new Kakashi();

uchihaClan = kakashi; // 型 'Kakashi' を型 'UchihaClan' に割り当てることはできません。
複数の型に、プライベート プロパティ 'sharingan' の異なる宣言が含まれています。

代入される側にのみ存在するフィールド、メソッドがある

うちは一族は火遁の術が使えますが、カカシは使えません。なので、以下のように実装を変更すると、互換性がなくなり、代入ができなくなります

class UchihaClan {
  public sharingan() {
    console.log('sharingan!!');
  }

  public katon() {
    console.log('katon!!');
  }
}

class Kakashi {
  public sharingan() {
    console.log('sharingan!!');
  }
}

let uchihaClan = new UchihaClan();
const kakashi = new Kakashi();

uchihaClan = kakashi; //プロパティ 'katon' は型 'Kakashi' にありませんが、型 'UchihaClan' では必須です。

まとめ

いかがでしたでしょうか。本記事では、TypeScriptの構造的部分型について具体的なコード例を挙げながらアニメ「NARUTO」を用いて説明しました。Javaなどの公称型と比較してTypeScriptの型システムは少し緩めに設計されているため、その仕様についてある程度理解しておくことが重要になります。