Slide 1

Slide 1 text

TypeScript入門 2024 雫石 卓耶(@sititou70)

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

TS Playgroundのおすすめの設定 1. Pluginsタブ -> Format On Saveを有効化、リロード 2. Format On Saveタブで以下を有効化 ○ Enable format on save ○ Prevent copy link on save 3. Command + sでフォーマットされる 14

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

基本的な型 28 参考: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;

Slide 29

Slide 29 text

リテラル型 ● 特定の値しか代入できない型 ● 数値リテラル型 / 文字列リテラル型 / booleanリテラル型 29 参考: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;

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

関数型(2/2) ● 型注釈で使うとしたらこんなかんじ ● 以下のように書くことも多いです。意味は同じです 34 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}`; }

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

any型(2/3) ● ほとんどの操作(例:呼び出す、プロパティにアクセスする、被演算子にする) ができる(!) ● ● したがって慎重に使うべきです ○ コードレビューなどでは相応の理由が求められることも 36 // 静的型検査でおこられない const msg: any = null; // 実行時エラーになる console.log("現在のメッセージは" + msg.length + "文字です");

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

型エイリアス ● 型に別名を付けられる ● 慣例として、型名は大文字から始める場合が多いです 38 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 };

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Class 42 インターフェースの例の出典: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("コウモリ");

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Interface vs 型エイリアス(1/2) ● どちらの記法もオブジェクトの型を定義できる 44 他の細かい違いは: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 };

Slide 45

Slide 45 text

Interface vs 型エイリアス(2/2) ● わりとその場のノリで使い分けています ● Interfaceは拡張される。型エイリアスはされない(エラーになる ● ライブラリの型がinterfaceで書かれており、ユーザー側でそれを拡張する……み たいな場面もたまにある 45 他の細かい違いは: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; }

Slide 46

Slide 46 text

Union ● 「または」の意味 ● 合併型*1、バリアント型*2、和型*3とも ● Stringのリテラル型と併用すると、列挙型のように扱うこともできる ● (これとは別にenum構文もありますが、あまり利用されません) 46 *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";

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

「?」を使って「undefinedになりうる」ことを伝える ● 49 interface Hoge { a?: number; } type Fuga = { b?: string; }; function fn(c?: boolean) { return c; }

Slide 50

Slide 50 text

他の表現 ● Unionを使うと同じようなことが実現できます*1 ● 「?」記法の方が短く簡単 50 *1:厳密には、プロパティが省略可能であることとundefinedになり得ることは違います。「?」記法は、場合によっては安全でないコードが書けてしまうため、undefinedとのUnionを使用すべきという意見もあります。プロジェクトの方針を確認しましょう。 type maybeNumber = number | undefined;

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

型の絞り込み:少し複雑な例 54 type User = { name: string }; // User型が与えられれば名前を返す。そうでなければ undefinedを返す function getUserName(maybeUser: any): string | undefined { if (typeof maybeUser !== "object") return; // maybeUserはobject | null型 if (maybeUser === null) return; // maybeUserはobject型 if (!("name" in maybeUser)) return; // maybeUserは{ name: unknown }型 if (typeof maybeUser.name !== "string") return; return maybeUser.name; }

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

演習2 ● 問題3:ジェネリクスを使って、以下のようなtriple関数を作成してください 63 const result1 = triple(123); // result1は[number, number, number]型 const result2 = triple("hello"); // result2は[string, string, string]型

Slide 64

Slide 64 text

部分型関係(1/4) ● サブタイプ関係、is-a関係とも ● 例えば次のような型があります 64 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; type Dog = { name: string; forefoot: "前足" };

Slide 65

Slide 65 text

部分型関係(2/4) ● Animal型の変数にBird型の値を代入できます 65 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const bird: Bird = { name: "スズメ", wings: "翼" }; const animal: Animal = bird; // できる console.log(`この動物は${animal.name}です`); // エラーにならない

Slide 66

Slide 66 text

部分型関係(3/4) ● 逆はできません 66 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const animal: Animal = { name: "スズメ" }; const bird: Bird = animal; // できない(型エラー) // もし↑を許すと、↓のようなコードが書けて、実行時エラーになる console.log(`この動物は${bird.wings}です`);

Slide 67

Slide 67 text

部分型関係(4/4) ● 「S型の値」を「T型の値」として扱っても安全であるとき*1 ○ SはTの部分型、TはSの上位型です ○ 以降、S ⊆ Tと書きます*2 ○ 「S型の値の集合」は、「T型の値の集合」の部分集合 と考えると分かりやすいかもしれません ● 例:Birdの値をAnimalの値として扱っても安全なので ○ BirdはAnimalの部分型、AnimalはBirdの上位型 ○ Bird ⊆ Animal ● 部分型関係は、変数への代入や、関数への引数の割り当てなど、プログラムの 様々な箇所で検査されます*3 67 参考: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節より。*2:一般的には「<:」という記号が用いられるのですが、本資料では簡単のために部分集合意味論をベースとして、このような表記にしまし た。*3:より正確には、割り当て可能関係が使用されています。 Animal Bird Dog

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

オブジェクトの部分型関係(1/2) ● 規則1:上位型にないプロパティが部分型に存在しても良い*1 69 *2:幅部分型付け(S-RcdWidth)、TAPL p.142より type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const bird: Bird = { name: "スズメ", wings: "翼" }; const animal: Animal = bird; // Bird ⊆ Animalなので許される console.log(animal.name); // エラーにならない

Slide 70

Slide 70 text

オブジェクトの部分型関係(2/2) ● 規則2:共通するプロパティについても部分型関係でなければならない*1 70 *1:深さ部分型付け(S-RcdDepth)、TAPL p.143より type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; type AnimalHouse = { resident: Animal }; type BirdHouse = { resident: Bird }; const birdHouse: BirdHouse = { resident: { name: "スズメ", wings: "翼" } }; const animalHouse: AnimalHouse = birdHouse; // BirdHouse ⊆ AnimalHouseなので許 される console.log(`この家の住人は${animalHouse.resident.name}です`);

Slide 71

Slide 71 text

配列の部分型関係 ● Bird ⊆ Animal ならば Bird[] ⊆ Animal[] ● S ⊆ T ならば S[] ⊆ T[] *1 71 *1:これは便利ですが安全ではありません。詳しくは:TypeScriptにおける配列の共変性 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const birdArray: Bird[] = [{ name: "スズメ", wings: "翼" }]; const animalArray: Animal[] = birdArray; // Bird[] ⊆ Animal[]なので許される for (const animal of animalArray) { console.log(`この動物は${animal.name}です`); }

Slide 72

Slide 72 text

関数の部分型関係(1/8) ● printBirdは、Birdを受け取ります 72 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; let printBird = (bird: Bird) => console.log(bird.name); const bird: Bird = { name: "スズメ", wings: "翼" }; printBird(bird);

Slide 73

Slide 73 text

関数の部分型関係(2/8) ● printBirdをprintAnimalで置き換えてもエラーになりません 73 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; let printBird = (bird: Bird) => console.log(bird.name); const printAnimal = (animal: Animal) => console.log(animal.name); printBird = printAnimal; const bird: Bird = { name: "スズメ", wings: "翼" }; printBird(bird); (animal: Animal) => void の値を (bird: Bird) => void の値として扱っても安全

Slide 74

Slide 74 text

関数の部分型関係(3/8) ● getAnimalは、Animalを返します 74 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; let getAnimal = (): Animal => ({ name: "スズメ" }); const animal = getAnimal(); console.log(`動物の名前: ${animal.name}`);

Slide 75

Slide 75 text

関数の部分型関係(4/8) ● getAnimalをgetBirdで置き換えてもエラーになりません 75 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; let getAnimal = (): Animal => ({ name: "スズメ" }); const getBird = (): Bird => ({ name: "スズメ", wings: "翼" }); getAnimal = getBird; const animal = getAnimal(); console.log(`動物の名前: ${animal.name}`); () => Bird の値を () => Animal の値として扱っても安全

Slide 76

Slide 76 text

関数の部分型関係(5/8) Bird ⊆ Animalであるとき ● (animal: Animal) => void の値を (bird: Bird) => void の値として扱っても安全 ○ ● () => Bird の値を () => Animal の値として扱っても安全 ○ 76

Slide 77

Slide 77 text

関数の部分型関係(6/8) Bird ⊆ Animalであるとき ● (animal: Animal) => void ⊆ (bird: Bird) => void ○ ● () => Bird ⊆ () => Animal ○ 77

Slide 78

Slide 78 text

関数の部分型関係(7/8) Bird ⊆ Animalであるとき ● (animal: Animal) => void ⊆ (bird: Bird) => void ○ 引数に着目すると、部分型の方に、上位型のAnimalが現れている(反変) ● () => Bird ⊆ () => Animal ○ 戻り値については、部分型の方に、部分型のBirdが現れている(共変) 78

Slide 79

Slide 79 text

関数の部分型関係(8/8) より一般的に、2つの関数型「(S1) => S2」と「(T1) => T2」について ● T1 ⊆ S1 かつ S2 ⊆ T2 ● ならば ● (S1) => S2 ⊆ (T1) => T2 79 参考:TAPL 15.2節, p.144

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

発展:名前的と構造的(1/3) ● Javaなどの型システムは名前的 ○ 型には必ず名前がある ■ 例:class Animal { … ○ 部分型関係はソースコード上に明示的に現れる ■ 例:class Bird extends Animal { … ● TSの型システムは構造的 ○ 名前の無い型を書ける ■ 例:{name: string} 型 ○ 部分型関係は型の構造上に直接的に定義される 82 参考:TAPL 19.3節

Slide 83

Slide 83 text

発展:名前的と構造的(2/3) ● 部分型関係に由来するエラーが発生したとき「部分型の値を上位型の値として 扱っているか」確かめましょうと言いました ○ 名前的な型システムでは:ソースコードを確認すれば書かれている ○ 構造的な型システムでは:型の構造を確認し、関係性を確認する ■ 部分型関係のルールを知らないとむずかしい 83

Slide 84

Slide 84 text

発展:名前的と構造的(3/3) 名前的部分型では、構造的に互換性があっても、宣言されていない関係は認められな い 84 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(); } }

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

Intersection(1/2) ● 「かつ」の意味 ● Unionの逆 ● 交差型とも 90 type A = number & unknown; // Aはnumber型 type B = number & never; // Bはnever型

Slide 91

Slide 91 text

Intersection(2/2) 91 *1:S-Inter3規則、TAPL p.161 type C = { a: number } & { b: string }; // C ⊆ { a: number } かつ C ⊆ { b: string }であるようなCを考える*1 // したがって、 // Cは{ a: number; b: string }型

Slide 92

Slide 92 text

演習3 ● 問題1: ○ filterBirdsは、Birdの配列をフィルタリングする関数です。 ■ 引数: ● birds:Birdの配列 ● shouldExtract:birdsの各要素を取り出すかを判定する関数 ■ 戻り値:フィルタリングされたBirdの配列 ○ filterBirdsに渡せるshouldExtractの実装を、次ページに示すサンプルコードの他 に2つ挙げてください ■ 各実装でshouldExtractの型は異なる必要があります ■ 実行時にJSのエラーになってはいけません ■ filterの結果は変わっても良いです 92

Slide 93

Slide 93 text

演習3 93 type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const birds: Bird[] = [ { name: "スズメ", wings: "翼" }, { name: "カラス", wings: "翼" }, ]; const shouldExtract = (bird: Bird): boolean => bird.name === "スズメ"; console.log(filterBirds(birds, shouldExtract)); type Animal = { name: string }; type Bird = { name: string; wings: "翼" }; const birds: Bird[] = [ { name: "スズメ", wings: "翼" }, { name: "カラス", wings: "翼" }, ]; const shouldExtract = (bird: Bird): boolean => bird.name === "スズメ"; console.log(filterBirds(birds, shouldExtract)); const filterBirds = ( birds: Bird[], shouldExtract: (bird: Bird) => boolean ): Bird[] => { const filteredBirds: Bird[] = []; for (const bird of birds) if (shouldExtract(bird)) filteredBirds.push(bird); return filteredBirds; };

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

発展:Utility Types ● 型の取り回しを便利にする型 ● 型を受け取って型を返します*1 ● たま〜〜〜に使います。今回は割愛 96 *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; } // まだまだある

Slide 97

Slide 97 text

余談:型パズル(1/4) ● TSの型システムはチューリング完全であることが知られています ○ つまり、型検査が停止しないかもしれない(!) ● 実際には、処理系によって制限が設定されており、停止します ○ ○ が、それを回避する型ハックもあります ○ 悪用すると型検査器を落とすこともできます(やめましょう) 97

Slide 98

Slide 98 text

余談:型パズル(2/4) ● 型だけで様々なプログラムが書けます ○ コンパイル後のJSが空(”use strict”とかimport / exportとかしかない) ● 電卓 98

Slide 99

Slide 99 text

余談:型パズル(3/4) ● Schemeインタプリタ 99

Slide 100

Slide 100 text

余談:型パズル(4/4) ● TSの型検査器自身 100

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

ライブラリの型定義はどこから来るのか(3/3) ● パターン3:自分で定義する ○ Ambient module宣言という手法で自分でライブラリに型定義を与えること ができます ○ 少しマニアックなので今日は触れません ● (パターン4:anyで妥協する) ○ (これも重要な逃げ道) 106

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

まとめ:この資料では以下のことを学びました ● TSは ○ Alt JS:JavaScriptにコンパイルされる ○ (静的な)型が付いたJSのスーパーセット ● 型注釈、型推論、number、string、boolean、symbol、null、undefined、bigint、リテラ ル型、オブジェクトリテラル型、配列型、タプル型、関数型、any、型エイリアス、 Class、Interface、Union、Optional、unknown、never、readonly、ジェネリクス、部分 型関係、型アサーション(as)、Intersection、Utility Types ● 周辺には便利なツールがいっぱいある ○ 上手に使いこなしましょう ● 型は友達 118

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

型パズルでもやって遊んでいてください(Level 4) ● 以下のような、指定した数までの素数に評価されるPrimes型を定義してください ● 入力される数値の範囲は自身で定義して構いません 123 type CasePrimes1 = Primes<300> // CasePrimes1は[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]型

Slide 124

Slide 124 text

演習の回答例 124

Slide 125

Slide 125 text

演習1、問題1の回答例 125 const user: { firstName: string; lastName: string; age: number; favoriteFoods: string[]; hasProgrammingExperience: boolean; } = { firstName: "太郎", lastName: "山田", age: 24, favoriteFoods: ["寿司", "ラーメン", "カレー"], hasProgrammingExperience: true, };

Slide 126

Slide 126 text

演習1、問題2の回答例 126 const getSelfIntroduction = (user: { firstName: string; lastName: string; age: number; favoriteFoods: string[]; hasProgrammingExperience: boolean; }) => `私の名前は${user.lastName}${user.firstName}です。年齢は${ user.age }歳です。好きな食べ物は${user.favoriteFoods.join( "と" )}です。プログラミングの経験${ user.hasProgrammingExperience ? "があります" : "はありません" }`;

Slide 127

Slide 127 text

演習2、問題1の回答例 127 type Subject = | { kind: "name"; payload: string } | { kind: "favorite_food"; payload: string[] };

Slide 128

Slide 128 text

演習2、問題2の回答例 128 function printSelfIntroduction(subject: Subject) { switch (subject.kind) { case "name": console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; default: const never: never = subject; never; } }

Slide 129

Slide 129 text

演習2、問題2の回答例2 129 function printSelfIntroduction(subject: Subject) { switch (subject.kind) { case "name": console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; default: subject satisfies never; } }

Slide 130

Slide 130 text

演習2、問題3の回答例 130 const triple = (arg: T): [T, T, T] => [arg, arg, arg];

Slide 131

Slide 131 text

演習3、問題1の回答例 ● shouldExtractに渡せる関数を考えたい ● ある関数をshouldExtractとして渡しても安全であるようにしたい ● 「(S1) => S2 型の値」を「(bird: Bird) => boolean 型の値」として扱っても安 全であるようにしたい ○ 「ある関数」を「(S1) => S2 型の値」とおいた ● (S1) => S2 ⊆ (bird: Bird) => boolean としたい ● Bird ⊆ S1 かつ S2 ⊆ boolean を満たせば良い ○ S1:Bird、Animal、unknownなど ○ S2:true、false、neverなど 131

Slide 132

Slide 132 text

演習3、問題1の回答例 132 const shouldExtract = (animal: Animal): boolean => animal.name === "スズメ"; const shouldExtract = (maybeBird: unknown): boolean => typeof maybeBird === "object" && maybeBird !== null && "name" in maybeBird ? maybeBird.name === "スズメ" : false; const shouldExtract = (_: Bird): true => true; const shouldExtract = (_: Bird): never => { while (true) {} };

Slide 133

Slide 133 text

演習3、問題3の回答例 133 (123 as unknown as string).charAt(0);