$30 off During Our Annual Pro Sale. View Details »

TypeScript

 TypeScript

2023年度リクルート エンジニアコース新人研修の講義資料です

Recruit
PRO

August 10, 2023
Tweet

More Decks by Recruit

Other Decks in Technology

Transcript

  1. TypeScript入門
    雫石 卓耶(@sititou70)

    View Slide

  2. 自己紹介
    ● 名前:雫石 卓耶(しずくいし たくや)(@sititou70)
    ● 2021年に新卒でリクルートに入社しました
    ● 所属:横断エンジニアリング部 アプリケーション・ソリューショ
    ン・グループ(ASG)
    ● 普段:TypeScript、React、Next.jsでWebアプリケーションを書
    いています
    ● 趣味:型パズルなど
    ● よろしくお願いします
    2
    フローリングの
    アイコンが私

    View Slide

  3. 研修で学ぶこと
    ● TS(TypeScript)とは何か(座学9、演習1)
    ● TSの型、コードの書き方(座学5、演習5)
    ● その他、周辺知識として知っておいたほうが良いこと(ほぼ座学のみ)
    3

    View Slide

  4. 研修のゴール
    ● 型と友だちになること
    ○ 型エラーは仕事を増やす敵ではなく
    ○ コードの不具合(将来の仕事)を減らしてくれる仲間であることを実感しましょう
    4

    View Slide

  5. 「それは既に知っているよ」という人
    ● スライドの最後にある型パズルでもやって遊んでいてください
    ○ 演習もやらなくて大丈夫です
    ■ Slackの進捗確認には「パス」でリアクションください
    ● または、任意のタイミングで研修に戻っても大丈夫です
    ● もしパズルが解けたら、せっかくなので研修の最後にでも共有してほしいです
    5

    View Slide

  6. 研修で学ぶこと
    ● TS(TypeScript)とは何か
    ● TSの型、コードの書き方
    ● その他、周辺知識として知っておいたほうが良いこと
    6

    View Slide

  7. TS(TypeScript)とは何か
    ● Microsoft社が開発しているプログラミング言語
    ● Alt JS:JavaScriptにコンパイルされる
    ● 「(静的な)型が付いたJSのスーパーセット」とよく言われる
    7

    View Slide

  8. Alt JS:JavaScriptにコンパイルされる(1/3)
    ● TSのコードはJSにコンパイルされます
    ○ 「トランスパイル」とも言われます
    ● 実行はJSエンジンが行います
    8

    View Slide

  9. Alt JS:JavaScriptにコンパイルされる(2/3)
    ● いろいろあるAlt JS
    ○ Flow, CoffeeScript, Haxe, PureScript, Reason…
    ● ブラウザで動かせる言語は基本的にJSだけだから
    ● 一時期はどのAlt JSが天下を取るかという状況だったが、最近はTSに軍配が上がりつ
    つある様子
    9
    Alt JSのリストの参考:https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS#javascript-extensions

    View Slide

  10. Alt JS:JavaScriptにコンパイルされる(3/3)
    ● 古いブラウザでも動くようにコンパイルすることもできます
    ● Downlevelingといいます
    ○ 例:async / awaitはそのままではIEでは動作しないが、TSが頑張ってコードを変換
    してくれる
    ● もちろん、「まったくDownlevelingしない」ということもできます
    ○ 例:tsは型検査だけで、コンパイルはBabelなど別のツールに任せる
    10

    View Slide

  11. TSは
    「(静的な)型が付いたJSのスーパーセット」
    11

    View Slide

  12. JSのスーパーセットとは
    ● すべてのJSのコードは、文法的に有効なTSのコードでもあります
    ○ 例:以下のようなJSのコードがあります
    ○ これをTSのPlaygroundにそのままコピペして動かせます
    ● JSから地続きで学習できるというメリット
    ● (JSの良くない部分を引き継ぐ場合もあるというデメリット)
    12
    const msgString = "hello world";
    console.log("現在のメッセージは" + msgString.length + "文字です");

    View Slide

  13. ミニ演習1
    1. TS Playgroundを開きます
    2. TS Config -> Type Checking -> strictが有効になっていることを確認します
    3. 上記のコードを実際にTS Playgroundへコピペしてみてください
    ○ 実行できますか
    ○ コンパイルの結果、どのようなJSになりますか
    ■ 右側の.JSタブから確認します
    13
    const msgString = "hello world";
    console.log("現在のメッセージは" + msgString.length + "文字です");

    View Slide

  14. TSは
    「(静的な)型が付いたJSのスーパーセット」
    14

    View Slide

  15. JSは動的な検査を行う言語(1/2)
    ● 実行するまでエラーがわからないのが辛い
    ● 1行目を次のように書き間違えたとします
    ● 実行するとエラーになります
    ● 2行目の「msgString.length」を実行しようとしてエラーになりました
    15
    const msgString = null;
    console.log("現在のメッセージは" + msgString.length + "文字です");
    TypeError: Cannot read properties of null (reading 'length')

    View Slide

  16. JSは動的な検査を行う言語(2/2)
    ● または、次のように間違えたとします
    ● こちらは実行してもエラーが出ません
    ● そういう仕様なので
    16
    const msgString = 12345;
    console.log("現在のメッセージは" + msgString.length + "文字です");
    現在のメッセージはundefined文字です

    View Slide

  17. 開発・テスト時に気づけるか……?
    ● 気づけないとユーザーのブラウザ上で実行時エラーになります
    ○ やばい
    17

    View Slide

  18. いやいや
    ● 「そもそもmsgStringは文字列が入る変数なんだから、nullや数値を代入している1行目
    の時点でおかしいでしょ」
    ○ ……と、思いましたよね?
    ○ msgStringという変数名から「この変数にはなんらかの文字列(文字列”型”の値)を
    入れるぞ」という作者の気持ちが伝わってきたからです
    ● TSの静的型検査器も同じようなことを検査してくれます
    ○ 私達のかわりにTSが同じことをやってくれます
    18

    View Slide

  19. TSで書いてみる
    ● すると、静的型検査で怒られました
    ● ソースコードを実行する前に、静的解析によってエラーを発見できました
    19
    const msg: string = null;
    console.log("現在のメッセージは" + msg.length + "文字です");
    型注釈:「msgは文字列が入る変数」とい
    うのをTSに教えている

    View Slide

  20. ミニ演習2
    ● 上記のコードをPlaygroundにコピペしてみてください
    ○ どのようなエラーが表示されますか
    ● msgに適当な文字列を代入するようにして、エラーを解消してみてください
    ● エラーを解消したプログラムはどのようなJSにコンパイルされますか
    ○ 特に型注釈の部分はどうなりますか
    20
    const msg: string = 12345;
    console.log("現在のメッセージは" + msg.length + "文字です");

    View Slide

  21. ありがたみが薄い?
    ● 今回のコードは簡単だったのでパッと見て不具合がわかった
    ● しかしこれが何万行もあったら……?
    ○ ※実際、プロジェクトにおけるTSのコードは数万行になりうる
    ● TSは、プログラムの各部分を、それが計算する値の種類によって分類することにより、
    プログラムがある種の振る舞い(例:nullや数値の.lengthを参照するといった動作)を起
    こさないことを検査してくれる*1
    ● 人の目で見るより、TSにやってもらったほうが効率的
    21
    *1:Pierce, Benjamin C. 型システム入門 プログラミング言語と型の理論. 株式会社 オーム社, 2013.(以降TAPLの略称で参照)の p.1にある型システムの定義から一部表現を借りています

    View Slide

  22. 注意:動的な検査が望ましい場合もある
    ● 複雑な静的型検査をやろうとすると
    ○ 難しい型パズルを書くことになったり
    ■ そのコードを後の人が見てわからないということになったり
    ○ そもそも型システムの表現力が足りず実現不可能だったり
    ● それよりは動的に検査してしまって、素直にエラー画面などを表示したほうが簡単という
    場合もあります
    ● やりたいことに応じて適切な手段を取れるようになりましょう
    22

    View Slide

  23. 研修で学ぶこと
    ● TS(TypeScript)とは何か
    ● TSの型、コードの書き方
    ● その他、周辺知識として知っておいたほうが良いこと
    23

    View Slide

  24. 型注釈
    ● TSに「ここは〜〜型ですよ」と教える方法
    ● いろいろなところに書けます
    24
    const a: string = "hoge";
    let b: string;
    var c: string;
    function greet(name: string): string {
    return "hello, " + name;
    }
    class MyClass {
    e: string = "";
    }

    View Slide

  25. 型推論
    ● その場の状況に合った「一番それっぽい型」をTSが考えてくれる機能
    ● 型推論により、型注釈は省略できる場合が多いです
    ○ 楽!
    25
    関連キーワード:型再構築

    View Slide

  26. しかし、わかりやすさのために型注釈を書くこともある
    26
    // 自明なので注釈は必須でない
    const num: number = 123;
    // dataの型が自明でないので注釈がほしい
    // 注釈があると、「長い処理」を書き間違ってしまったときにここでエラーが出る
    // 注釈を消して型推論に任せると、どこか別の場所でエラーが出てデバッグが大変になる
    かも
    const data = hogeArray.map(/* 長い処理 */).filter(/* 長い処理
    */).reduce(/* さらにつづく...

    View Slide

  27. 基本的な型
    27
    参考:https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#built-in-types
    const num: number = 123;
    const str: string = "hoge";
    const bool: boolean = true;
    const sym: symbol = Symbol("fuga");
    const nullObj: null = null;
    const undef: undefined = undefined;
    const bigint: bigint = 9007199254740991n;

    View Slide

  28. リテラル型
    ● 特定の値しか代入できない型
    ● 数値リテラル型 / 文字列リテラル型 / booleanリテラル型
    28
    参考:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types
    const oneHundred: 100 = 100;
    const helloMsg: "hello world" = "hello world";
    const trueVal: true = true;
    const falseVal: false = false;

    View Slide

  29. constとletにおける型推論の違い
    ● 変数がconstの場合、リテラル型に推論される
    ● 変数がletの場合、リテラル型にはならない
    29
    aは”hello”以外の値にならない
    bは今後”hello”以外の値になるかもしれない

    View Slide

  30. オブジェクトリテラル型
    ● 特定のプロパティを持ったオブジェクトを表す
    ○ よく使います
    30
    const obj: { num: number; str: string } = {
    num: 12345,
    str: "piyo",
    };

    View Slide

  31. 配列型
    ● タプル型:Array型の一種
    ○ 各要素の型、順番、個数を正確に把握している型
    31
    const array1: number[] = [1, 2, 3];
    // または
    const array2: Array = [1, 2, 3];
    const array3: [number, string, true] = [123, "hello", true];

    View Slide

  32. 関数型(1/2)
    ● 型は次のように表される
    32
    (num: number, bool: boolean) => string
    // 引数がない時
    () => string
    // 戻り値が無い時
    (num: number, bool: boolean) => void

    View Slide

  33. 関数型(2/2)
    ● 型注釈で使うとしたらこんなかんじ
    ● 以下のように書くことも多いです。意味は同じです
    33
    const fn1: (num: number, bool: boolean) => string =
    (num, bool) => `${num}${bool}`;
    const fn2 = (num: number, bool: boolean): string => `${num}${bool}`;
    function fn3(num: number, bool: boolean): string {
    return `${num}${bool}`;
    }

    View Slide

  34. any型(1/3)
    ● どのような値も代入できる
    34
    const any1: any = 12345;
    const any2: any = "hogehoge";
    const any3: any = (a) => a;

    View Slide

  35. any型(2/3)
    ● ほとんどの操作(例:呼び出す、プロパティにアクセスする、被演算子にする)ができる
    (!)

    ● したがって慎重に使うべきです
    ○ コードレビューなどでは相応の理由が求められることも
    35
    // 静的型検査でおこられない
    const msg: any = null;
    // 実行時エラーになる
    console.log("現在のメッセージは" + msg.length + "文字です");

    View Slide

  36. any型(3/3)
    ● anyあるある
    ○ 既存のJSプロジェクトにTSを導入している。とりあえず導入だけ済ませて、型は後
    から書きたい
    ○ JSのライブラリをTSで使いたいが、型定義が不足している。自分で定義を書くのは
    時間がかかるのでいったんanyにしておきたい
    ○ 複雑な型エラーが出ていてよくわからん!詳しい人、後で助けてくれ!!!
    ● うまく使えば非常ハッチとして役立つ
    ○ しかし「よくわからないから」で放置してしまうと……
    36

    View Slide

  37. 型エイリアス
    ● 型に別名を付けられる
    ● 慣例として、型名は大文字から始める場合が多いです
    37
    type MyName = string;
    const myName: MyName = "taro yamada";
    type Circle = { pos: { x: number; y: number }; r: number };
    const circle: Circle = { pos: { x: 1, y: 2 }, r: 3 };

    View Slide

  38. 演習1
    ● 問題1:次のuser変数に型注釈を付けてください
    38
    const user = {
    firstName: "太郎",
    lastName: "山田",
    age: 24,
    favoriteFoods: ["寿司", "ラーメン", "カレー"],
    hasProgrammingExperience: true,
    };

    View Slide

  39. 演習1
    ● 問題2:次のようなgetSelfIntroduction関数を書いてみてください
    ○ 引数:1つ。先程のuserオブジェクト。引数名は任意
    ○ 戻り値: string
    ■ 次のような自己紹介文。[ ]の中は引数によって変化する部分
    ■ 私の名前は[姓][名]です。年齢は[年齢]歳です。好きな食べ物は[〜〜と〜〜
    と〜〜(〜〜には好きな食べ物配列の各要素が入る。要素数だけ繰り返す)]
    です。プログラミングの経験[があります or はありません]
    ● 問題3:getSelfIntroduction関数を実際に実行し、consoleに自己紹介文を出力してみ
    てください
    39

    View Slide

  40. Class
    ● JSにあるClass構文をTSでも使えます
    ● 宣言したClassは型としても使えます
    ○ Javaとかと一緒
    40

    View Slide

  41. Class
    41
    インターフェースの例の出典:https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%95%E3%82%A7%E3%83%BC%E3%82%B9_(%E6%8A%BD%E8%B1%A1%E5%9E%8B)
    interface Flyable {
    fly(): void;
    }
    class Bird implements Flyable {
    name: string;
    constructor(name: string) {
    this.name = name;
    }
    fly() {
    console.log(`${this.name}「パタパタ」`);
    }
    }
    const bat: Bird = new Bird("コウモリ");

    View Slide

  42. JSのClassに無い機能
    ● 抽象クラス / 抽象メソッドを定義できます
    ● Interfaceを実装できます
    42

    View Slide

  43. Interface vs 型エイリアス(1/2)
    ● どちらの記法もオブジェクトの型を定義できる
    43
    他の細かい違いは:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces
    interface Obj1 {
    prop: 123;
    }
    const obj1: Obj1 = { prop: 123 };
    type Obj2 = {
    prop: 123;
    };
    const obj2: Obj2 = { prop: 123 };

    View Slide

  44. Interface vs 型エイリアス(2/2)
    ● わりとその場のノリで使い分けています
    ● Interfaceは拡張される。型エイリアスはされない(エラーになる
    ● ライブラリの型がinterfaceで書かれており、ユーザー側でそれを拡張する……みたいな
    場面もたまにある
    44
    他の細かい違いは:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces
    interface Hoge {
    a: number;
    }
    interface Hoge {
    b: string;
    }
    どっちも同じ
    interface Hoge {
    a: number;
    b: string;
    }

    View Slide

  45. Union
    ● 「または」の意味
    ● 合併型*1、バリアント型*2、和型*3とも
    ● Stringのリテラル型と併用すると、列挙型のように扱うこともできる
    ● (これとは別にenum構文もありますが、あまり利用されません)
    45
    *1:Unionと同じ意味。*2:タグ付きのような「互いに素」なUnionを特に指す。*3:2つの型に対するバリアント型をいう。TAPL, 11.10節, p.108より
    type NumOrStr = string | number;
    type MaybeStr = string | null | undefined;
    type Colors = "red" | "blue" | "green" | "yellow";

    View Slide

  46. Optional
    ● JavaScriptのプログラムでよくあるバグは、nullまたは未定義の変数に対して、値が存在
    する前提でアクセスしてしまうケース
    ● (余談)Tony Hoareいわく「Null参照は最大の誤りだった。コンパイルですべてチェックで
    きるようにしたかった」
    ○ Null参照:10億ドルの間違い
    46

    View Slide

  47. 朗報:TSではstrictNullChecksがデフォルトで有効
    ● nullまたはundefinedを厳密に扱ってくれるオプション
    ○ 例えばundefinedになりうるオブジェクトのプロパティを参照しようとすると警告してく
    れます
    47
    参考:https://www.typescriptlang.org/tsconfig#strictNullChecks

    View Slide

  48. 「?」を使って「undefinedになりうる」ことを伝える

    48
    interface Hoge {
    a?: number;
    }
    type Fuga = {
    b?: string;
    };
    function fn(c?: boolean) {
    return c;
    }

    View Slide

  49. 他の表現
    ● Unionを使うと同じようなことが実現できます
    ● 「?」記法の方が短く簡単
    49
    type maybeNumber = number | undefined;

    View Slide

  50. 型の絞り込み(1/2)
    ● Union型は、if文やswitch文によって値をチェックしていくことで、より詳細な型へ絞り込
    まれます
    50
    function fn(a?: number) {
    // ここでaはnumber | undefined型。以下はおこられる
    Math.sqrt(a);
    if (a === undefined) return;
    // ここではnumber型。以下はおこられない
    Math.sqrt(a);
    }

    View Slide

  51. 型の絞り込み(2/2)
    ● 他にも以下のような構文で絞り込まれます
    ○ typeof x === “string”
    ○ x instanceof HogeClass
    ○ リテラル型の同値確認( if (x === “some message”) )
    ○ obj.x や obj[“x”] や x in objによるプロパティの存在確認
    ● 上記だけでは十分でない場合、ユーザー定義の型ガードを使用する手もあります
    ○ 詳しくは割愛。必要になったら調べましょう
    51

    View Slide

  52. readonly
    ● 書き換え操作を型検査で抑止します
    ○ ランタイムのJSからは書き換え可能
    52
    type Obj = {
    readonly prop: string;
    };
    const obj: Obj = {
    prop: "hello",
    };
    obj.prop = "goodbye"; // おこられる

    View Slide

  53. unknown型 / never型
    ● unknown型
    ○ どのような値も代入できる
    ○ できる操作は少ない:よく分からない値なので迂闊に扱えない
    ■ any型の値はそこから何でもできた
    ■ unknown型の値は、それを具体的な値に絞り込まないと何もできないので安

    ● never型
    ○ どのような値も代入できない
    ○ 型の絞り込みの結果、到達不能な部分などで現れる
    53

    View Slide

  54. ジェネリクス(1/4)
    ● 総称性、パラメータ(パラメトリック)多相とも
    ● いろいろな型に対応するプログラムが書ける能力
    54

    View Slide

  55. ジェネリクス(2/4)
    ● 引数をそのまま返すid関数を作りたいとします
    ● すべての型に対してid関数のバリエーションを作るのは面倒
    55
    const id = (arg) => arg;
    const idBoolean = (arg: boolean): boolean => arg;
    const idNumber = (arg: number): number => arg;
    const idString = (arg: string): string => arg;
    // ...

    View Slide

  56. ジェネリクス(3/4)
    ● unknownと型を付けて実装をまとめてもいいけれど、戻り値の型もunknownになってし
    まいます
    56
    const id = (arg: unknown): unknown => arg;
    // resultはunknown型に
    const result = id(123);

    View Slide

  57. ジェネリクス(4/4)
    ● ジェネリクスを使うと、以下のようにできます
    57
    // 型変数Tで引数の型をキャプチャ
    const id = (arg: T): T => arg;
    // resultはnumber型
    const result = id(123);
    // または型推論に任せて
    const result = id(123);

    View Slide

  58. 演習2
    ● 問題1:以下のgetUserName関数を完成させてください
    ○ ヒント:途中で「object & Record<"name", unknown>」という型を見るかもしれませ
    ん。これは「{ name: unknown }」のことです
    ■ 「&」の意味については後述します
    58
    type User = { name: string };
    // User型が与えられれば名前を返す。そうでなければ
    undefinedを返す
    function getUserName(maybeUser: unknown): string | undefined {
    // TODO: ここでmaybeUserを絞り込む
    return maybeUser.name;
    }

    View Slide

  59. 演習2
    ● 問題2:以下のprintSelfIntroduction関数で使用されているSubject型を書いてみてくだ
    さい。関数の仕様は次ページにあります
    59
    function printSelfIntroduction(subject: Subject) {
    switch (subject.type) {
    case "name":
    console.log(`私の名前は${subject.payload}です`);
    return;
    case "favorite_food":
    console.log(`私の好きな食べ物は${subject.payload.join("と")}です`);
    return;
    }
    }

    View Slide

  60. 演習2
    ● 引数
    ○ subject: 自己紹介の主題。typeとpayloadの2つのプロパティをもつ
    ■ payload: typeによって変わる
    ■ type: 文字列。”name”または”favorite_food”
    ● “name”: payloadには名前(文字列)が入る。
    ● “favorite_food”: payloadには好きな食べ物の名前の配列(文字列の配
    列)が入る。
    ● 戻り値
    ○ なし
    60

    View Slide

  61. 演習2
    ● 問題3:printSelfIntroduction関数で”favorite_food”ケースの実装を忘れてしまったとし
    ます。現状では何の型エラーも起きません。ケースの実装漏れでエラーが起きるように
    関数を改良してください
    ○ ヒント:すべてのケースが実装されている場合、default節におけるsubjectの型は
    ……?
    61

    View Slide

  62. 演習2
    ● 問題4:ジェネリクスを使って、以下のようなtriple関数を作成してください

    ● 問題5:「triple(triple(triple(123)))」をconsole.logに出力して観察しましょう。また、戻り
    値の型はどのようになっているか観察しましょう
    62
    triple(123) // [number, number, number]
    triple("hello") // [string, string, string]

    View Slide

  63. 部分型関係(1/2)
    ● 例えば次のような型があります
    ● Animal型の変数にBird型の値を代入しても安全です
    ● Javaの教科書とかでよく見るやつですね
    63
    type Animal = { name: string };
    type Bird = { name: string; wings: "翼" };
    type Dog = { name: string; forefoot: "前足" };
    const bird: Bird = { name: "スズメ", wings: "翼" };
    const animal: Animal = bird;
    console.log(`この動物は${animal.name}です`);

    View Slide

  64. 部分型関係(2/2)
    ● S型の値をT型の値として扱っても安全であるとき*1
    ○ SはTの部分型、TはSの上位型です
    ○ SとTは部分型関係にあります
    ■ サブタイプ関係、is-a関係とも
    ○ S <: Tと書きます(紙面の節約のため。TSの文法ではない)
    ● 例:
    ○ BirdはAnimalの部分型、AnimalはBirdの上位型
    ○ BirdとAnimalは部分型関係にあります
    ○ Bird <: Animal
    ● 再掲:部分型の値は上位型の値として扱っても安全
    64
    参考:principle of safe substitution(安全代入の原則、または安全代用の原則)、TAPL 15.1節。参考:subsumption principle(包摂原則)、Harper, Robert. Practical foundations for programming languages. Cambridge University Press(PFPL), 2016., Chapter
    24。*1:より正確には「型Sの任意の項が、型Tの項が期待されている文脈で安全に使用可能であるとき」、TAPL 15.1節より。

    View Slide

  65. 部分型関係の性質
    ● 反射的
    ○ Animal <: Animal
    ○ Bird <: Bird
    ○ Dog <: Dog
    ● 推移的*1
    ○ Chihuahua <: Dog <: Animal
    ○ ならば
    ○ Chihuahua <: Animal
    ● 前順序(pre-order)とも
    65
    参考:TAPL 15.2節 p.142。*1:TSの代入可能関係は一部で推移的ではないようなのでご注意ください。詳細:https://github.com/sititou70/ts-extends-hierarchy。また、部分型関係と代入可能関係の違いについて

    View Slide

  66. オブジェクトの部分型関係
    ● 規則1:共通するプロパティについても部分型関係でなければならない*1
    ● 規則2:上位型にないプロパティが部分型に存在しても良い*2
    ● 例:
    66
    *1:深さ部分型付け(S-RcdDepth)、TAPL p.143より。*2:幅部分型付け(S-RcdWidth)、TAPL p.142より
    {
    myName: string;
    partner: Dog;
    }
    <:
    { partner: Animal }

    View Slide

  67. 配列の部分型関係
    ● S <: T
    ● ならば
    ● S[] <: T[]
    ● です*1
    67
    *1:これは便利ですが安全ではありません。詳しくは:TypeScriptにおける配列の共変性

    View Slide

  68. 関数の部分型関係
    ● T1 <: S1 かつ S2 <: T2
    ● ならば
    ● (S1) => S2 <: (T1) => T2
    ● です
    ● ちょっと直感的ではないです。後の演習で試して理解するのが早いと思います
    68
    参考:TAPL 15.2節, p.144

    View Slide

  69. 部分型関係のグラフ*1
    ※今日紹介した型しか載せていません。本当はもう少し複雑です
    69
    *1:厳密には代入可能関係のグラフです。この図を生成したコード、および完全なグラフは次のリポジトリにあります:https://github.com/sititou70/ts-extends-hierarchy
    上位型
    部分型

    View Slide

  70. 部分型関係まとめ
    ● 以下のようなエラーメッセージを見かけたら
    ○ type '〜〜' is not assignable to type '〜〜'
    ● まずは部分型関係を思い出してみましょう
    ● エラーが出ている部分について、コードが安全かどうか確かめましょう
    ○ 安全か:部分型の値を上位型の値として扱っているか
    70

    View Slide

  71. 発展:構造的と名前的
    ● TSの型システムは構造的。部分型関係は構造の上に直接定義される
    ● 一方で、Javaなどの型システムは名前的。部分型関係はextendsなどで明示的に宣言さ
    れる。構造的に互換性があっても、宣言されていない関係は認められない
    71
    参考:TAPL 19.3節
    class Super {}
    class Sub1 extends Super {
    String prop;
    }
    class Sub2 extends Super {
    String prop;
    }
    class Main {
    public static void main(String args[]) {
    // 認められない
    Sub1 s = new Sub2();
    }
    }

    View Slide

  72. 型アサーション(as)
    ● 値の型をキャストできます
    ● 例*1
    72
    *1:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions より
    const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

    View Slide

  73. アップキャスト
    ● source as Targetのとき、Source <: Targetであること
    ● 例:dog as Animal
    ● 常に安全
    ○ Dogの値をAnimalとして扱っても何の問題もない
    ● そもそも必要ないことも
    73
    // as Animalは必要ない
    const animal: Animal = dog as Animal;

    View Slide

  74. ダウンキャスト
    ● source as Targetのとき、Target <: Sourceであること
    ● 例:animal as Dog
    ● 危険!
    ○ Animalの値をDogとして扱う
    ○ Dogにしかないプロパティにアクセスしたら実行時エラーになる
    ● 先程の例のように、相当の理由と自信がある場合のみ使ってください
    74
    const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

    View Slide

  75. 部分型関係にない型へのキャストは認められない
    ● これは認められない
    ○ dog as Bird
    ● しかし、いったん上位型を経由することでキャストできてしまいます
    75
    // これはできる
    dog as Animal as Bird
    // またはより一般的に
    dog as any as Bird
    dog as unknown as Bird

    View Slide

  76. もちろんこれもできる
    ● めちゃくちゃです
    ○ 本当の本当にマジで必要な時以外はやらないでください
    ● この「いったん上位型を経由して部分型関係にない型にキャストすること」は「愚かな
    キャスト」とも呼ばれます*1
    ● ちなみにJavaでは「ClassCastException」として認められません
    76
    *1:一部の界隈でだけかもしれません。Featherweight Java、TAPL 19.4節より
    dog as unknown as HTMLCanvasElement

    View Slide

  77. Intersection
    ● 「かつ」の意味
    ● Unionの逆
    ● 交差型とも
    77
    *1:S-Inter3規則、TAPL p.161
    type A = number & unknown; // number
    type B = number & never; // never
    type C = { a: number } & { b: string };
    // C <: { a: number } かつ C <: { b: string }であるようなCは?*1
    // C = { a: number; b: string }

    View Slide

  78. 演習3
    ● 問題1:Dogの配列がAnimalの配列に代入可能であることを確かめるコードを書いてく
    ださい
    78

    View Slide

  79. 演習3
    ● 問題2:以下のコードを使って、関数の部分型関係を検証してください
    79
    type Animal = { name: string };
    type Dog = { name: string; forefoot: "前足" };
    const dog: Dog = { name: "イヌ", forefoot: "前足" };
    let dogToAnimal = (dog: Dog): Animal => dog;
    // TODO: ここでdogToAnimalを、その部分型の関数で代用してみてください
    // ヒント:関数の部分型関係の定義より、代用する関数の型は次のようにできるでしょう
    // 引数の型:元の関数における引数の上位型
    // 戻り値の型:元の関数における戻り値の部分型
    dogToAnimal = ここを埋める;
    const animal: Animal = dogToAnimal(dog);

    View Slide

  80. 演習3
    ● 問題3:以下はそれぞれどのような型でしょうか。確かめてみてください
    80
    type D = { prop: { a: number } } & { prop: { b: string } };
    type E = number & 12345;
    type F = "Love" & "Peace";

    View Slide

  81. 演習3
    ● 問題4:asを使って、型検査に合格するが実行時エラーになるコードを書いてみてくださ

    81

    View Slide

  82. 発展:Utility Types
    ● 型の取り回しを便利にする型
    ● 型を受け取って型を返します*1
    ● たま〜〜〜に使います。今回は割愛
    82
    *1:型演算子とも。ただし高階の型演算子はTSにないです。工夫してそれっぽいものを実現することはできます。参考:https://github.com/gvergnaud/hotscript
    type A = Partial<{ a: number }>; // { a?: number }
    type B = Required<{ a?: number }>; // { a: number }
    type C = Pick<{ a: number; b: string }, "b">; // { b: string }
    type D = Omit<{ a: number; b: string }, "b">; // { a: number }
    type E = Readonly<{ a: number }>; // { readonly a: number; }
    // まだまだある

    View Slide

  83. 余談:型パズル(1/2)
    ● TSの型システムはチューリング完全であることが知られています
    ○ つまり、型検査が停止しないかもしれない(!)
    ● 実際には、処理系によって制限が設定されており、停止します

    ○ が、それを回避する型ハックもあります
    ○ 悪用すると型検査器を落とすこともできます(やめましょう)
    83

    View Slide

  84. 余談:型パズル(2/2)
    ● 型だけで様々なプログラムが書けます
    ○ コンパイル後のJSが空(”use strict”とかimport / exportとかしかない)
    ● 電卓
    ● 平方根の近似
    ● TSの型検査器自身
    84

    View Slide

  85. 余談:型パズル(2/2)
    85
    やばい

    View Slide

  86. 研修で学ぶこと
    ● TS(TypeScript)とは何か
    ● TSの型、コードの書き方
    ● その他、周辺知識として知っておいたほうが良いこと
    86

    View Slide

  87. ライブラリの型定義
    ● JSのプロジェクトでは、ほぼ確実に外部のライブラリを使用します
    ● 通常は、npm(Node.jsのパッケージマネージャ)に公開されているライブラリを利用する
    ケースが多いです
    ● 外部ライブラリをTSから利用するためには、「型定義ファイル」が必要です
    87

    View Slide

  88. 型定義ファイルとは
    ● TSコードのうち、型の定義に関する情報だけを抽出したもの
    ● 拡張子は.d.ts
    ● 例:「jQueryの型情報が表示されないんだけど、.d.tsファイル入れ忘れてね?」
    ○ みたいによく言う
    ● 演習:適当な型定義ファイルを生成してみましょう
    ○ Playgroundに適当なTSコードを貼り付けて、.D.TSタブを開いてみる
    88

    View Slide

  89. ライブラリの型定義はどこから来るのか(1/3)
    ● 大きく3パターンあります
    ● パターン1:パッケージに同梱されている
    ○ ライブラリをnpm installした時点で型定義が使えるようになっています
    ○ この手のやつは、パッケージのpackage.jsonに「“types”: “index.d.ts”」といった記載
    があります
    ○ npmに以下のようなバッジがあります
    89

    View Slide

  90. ライブラリの型定義はどこから来るのか(2/3)
    ● パターン2:第3者が提供してくれている(DefinitelyTyped)
    ○ npm i @types/(ライブラリ名) でインストールします
    ○ npmにバッジがあります
    90

    View Slide

  91. ライブラリの型定義はどこから来るのか(3/3)
    ● パターン3:自分で定義する
    ○ Ambient module宣言という手法で自分でライブラリに型定義を与えることができま

    ○ 少しマニアックなので今日は触れません
    ● (パターン4:anyで妥協する)
    ○ (これも重要な逃げ道)
    91

    View Slide

  92. 型定義が壊れていたら
    ● 提供された型定義が本体のAPIを網羅していないことも度々ある
    ○ e.g. 特定の関数の型定義がごっそり抜けてる / anyだらけ / etc...
    ● コントリビューションチャンス!
    ○ 型定義を修正するPRを出して世の中に貢献しましょう
    92

    View Slide

  93. モジュール
    ● JSには複数のモジュールのスタイルがあります(歴史的事情)
    ○ ES2015のモジュール
    ■ 例:import hoge from “module-name”;
    ■ ECMAScript 2015というバージョンで初めてモジュールに関する構文が仕様化されました
    ■ それまではJSにモジュールという概念は無かった
    ○ Common JSのモジュール
    ■ 例:const hoge = require(“module-name”);
    ■ ES2015以前から、Node.jsなどで使われていたスタイル
    ○ 他にも色々
    ● JSの実行環境によってモジュールのスタイルが異なるのが現状
    ○ したがって、利用するモジュールローダー(モジュールを読み込む仕組み)も使い分ける必要があり
    ます
    93

    View Slide

  94. TSのモジュール事情
    ● 基本はESスタイルで記述
    ● コンパイル時にランタイムで利用したいモジュールローダーを指定すると、うまいこと変換してく
    れます
    ● tsconfig.jsonのmodule:利用したいモジュールローダーを指定
    ○ commonjs:Node.jsで実行したい場合など
    ○ esnext:ブラウザのを使う場合など<br/>○ 他の値も選択可能だが、使うケースは正直少ない<br/>● 演習<br/>○ importとexportを使って適当なTSのコードを書いてください<br/>○ Playgroundのmoduleの設定をcommonjsとesnextで切り替えたとき、生成されるJSはどの<br/>ように変化しますか<br/>94<br/>

    View Slide

  95. ビルド
    ● JavaScriptのビルドの歴史は割と闇が深い
    ● 太古の昔
    ○ AltJSが流行る前は、開発者が書いた.jsファイルをそのままHTMLのタグで読み<br/>込んでいました<br/>● よりリッチな体験がwebに求められるにつれ、読み込まれるJSファイルは重厚長大化<br/>● これにともなって、様々な課題が浮上<br/>○ JSファイルを分割したいが、<script>タグによる読み込み回数は増やしたくない<br/>○ 別言語(AltJS)で開発したい<br/>○ ダウンロードサイズ削減のため、JSファイルを圧縮したい<br/>○ etc…<br/>● いろいろなツールが開発され、吟味され、混乱もあり、いろいろあって<br/>……<br/>95<br/>

    View Slide

  96. モジュールバンドラという概念が生まれた
    ● 以下のようなことをやってくれるツール
    ○ importとexportを解析、必要な複数のJSファイルを1つにまとめる
    ○ tree shaking:importしたけど利用していないライブラリをバンドル結果から除外
    ○ ソースマップ:元のコードとバンドル結果の対応関係を表すファイル.デバッグに役立つ
    ○ バンドル結果の圧縮、難読化
    ○ Alt JSのコンパイル
    ● バンドラもいろいろある
    ○ webpack, rollup.js, fusebox, Parcel, esbuild, etc…
    ● 近年はwebpackが広く使われている印象
    96

    View Slide

  97. 最近だと
    ● ECMAScriptのimport/export構文やブラウザのといった標準<br/>化が進み、バンドラに頼らなくともモジュールが利用可能に<br/>● とはいえ、import の連鎖が深くなると、ブラウザが最後のモジュールに到達するまでに<br/>時間がかかってしまいます<br/>○ この意味で、ファイルをまとめるモジュールバンドラは当分必要とされつづけると思<br/>われます<br/>97<br/>

    View Slide

  98. 遅延と投機
    ● 一概にすべてのJavaScriptファイルを単一ファイルにバンドルすればよい、という訳では
    ない
    ● 適切な使い分けが重要です
    ○ 「事前に必要となることがわかっているモジュール」を投機的にfetchする
    ○ 「ユーザーにとって本当に必要となるまで」ダウンロードを遅延させる
    98

    View Slide

  99. 周辺のツールチェイン:TypeScript自体の便利機能
    ● TypeScript本体に付属しているtscというツール自体にも便利機能があります
    ○ --noEmit : JSの生成を行なわずに、型検査のみを実行するオプション. CI等に組み
    込むと便利
    ○ --noUnusedLocals、--noUnusedParameters: 利用されていない変数を検知してエ
    ラーにするオプション. 冗長なコードの削減になる
    ● その他にも、tscには様々なオプションが用意されています。公式ドキュメントを見てみる
    とよいでしょう
    99

    View Slide

  100. 周辺のツールチェイン:ESLint
    ● AST(抽象構文木)を用いたコードのチェックツール。禁止したいコードパターンをルール
    として登録して利用します
    ● ルールの例
    ○ consoleを使わないこと
    ○ 再代入しない変数にはletではなくconstを用いること
    ● TypeScriptとESLintを併用する場合、
    https://github.com/typescript-eslint/typescript-eslintを使います
    100

    View Slide

  101. 周辺のツールチェイン:Prettier
    ● ソースコードを機械的にフォーマットするためのツール
    ● 下記のような不毛な議論に左右されなくなります
    ○ 「文字列リテラルはシングルクォート or ダブルクォート」
    ○ 「セミコロンを文の末尾につけるべきか」
    ● ファイルの保存時やコミット時に自動的にフォーマットが走る設定にしておくと便利です
    ○ ファイルの保存時:VSCodeの設定
    ○ コミット時:Git Hookの設定、例えばHuskyを使うなど
    ● Prettierは標準でTypeScriptに対応
    101

    View Slide

  102. 周辺のツールチェイン:自動生成等
    ● I/Oやネットワーク通信など、外部との境界ではanyやunknownが発生しがち
    ○ 実際、外部システムとインターフェースの認識がずれていた、または認識はあって
    いたけど実装がずれていた、みたいな障害がありがちです
    ● 使用する通信プロトコルに合わせて、IDL(インターフェース記述言語)からTypeScriptの
    型を生成するツールも多数存在します
    ○ OpenAPI(Swagger), GraphQL, TypeORM, Prisma, etc…
    102

    View Slide

  103. まとめ:この資料では以下のことを学びました
    ● TSは
    ○ Alt JS:JavaScriptにコンパイルされる
    ○ (静的な)型が付いたJSのスーパーセット
    ● 周辺には便利なツールがいっぱいある
    ○ 上手に使いこなしましょう
    ● 型は友達
    103

    View Slide

  104. 参考文献
    ● TypeScriptの公式ドキュメント
    ○ https://www.typescriptlang.org/docs/
    ● Pierce, Benjamin C. 型システム入門 プログラミング言語と型の理論. 株式会社 オーム
    社, 2013.
    ○ TAPLの略称でも参照している
    104

    View Slide

  105. 型パズルでもやって遊んでいてください(Level 1)
    ● 以下のようなIf型を定義してください
    105
    type CaseIf1 = If // 'a'
    type CaseIf2 = If // 'b'
    出典:https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md

    View Slide

  106. 型パズルでもやって遊んでいてください(Level 2)
    ● 以下のようなReverse型を定義してください
    106
    type CaseReverse1 = Reverse<['a', 'b']> // ['b', 'a']
    type CaseReverse2 = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a']
    出典:https://github.com/type-challenges/type-challenges/blob/main/questions/03192-medium-reverse/README.md

    View Slide

  107. 型パズルでもやって遊んでいてください(Level 3)
    ● 以下のような、0以上の整数の掛け算を行うMultiply型を定義してください
    ● 入力される数値の範囲は自身で定義して構いません
    107
    type CaseMultiply1 = Multiply<2, 3> // 6
    type CaseMultiply2 = Multiply<4, 5> // 20
    type CaseMultiply3 = Multiply<6, 0> // 0

    View Slide

  108. 型パズルでもやって遊んでいてください(Level 4)
    ● 以下のような、指定した数までの素数に評価されるPrimes型を定義してください
    ● 入力される数値の範囲は自身で定義して構いません
    108
    type CasePrimes1 = Primes<300> // [2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
    31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103,
    107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179,
    181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257,
    263, 269, 271, 277, 281, 283, 293]

    View Slide