Share
Embed
Transcript
165 slides
-
自己紹介 • 名前:雫石 卓耶(しずくいし たくや)(@sititou70) • 2021年に新卒でリクルートに入社 • 所属: ◦
株式会社インディードリクルートテクノロジーズ ◦ Technology 3部 • 普段:チームのテックリードとして設計やレビューなど • 趣味:型パズルなど • よろしくお願いします フローリングの アイコンが私 2 -
自己紹介(サポート講師) • 名前:佐藤 昭文(さとう あきふみ)@akfm_sato • 所属: ◦ 株式会社リクルート ◦
住まい(KR開発) • 普段:Next.jsを中心に色々 • 趣味:記事や登壇・筋トレ・車・服(洗濯ガチ勢になり あっきーさん たい) 3 -
自己紹介(サポート講師) • 名前:眞野 隼輔(まの しゅんすけ)@progfay • 株式会社リクルートに2020年新卒入社 • 所属: ◦
株式会社リクルート ◦ ビューティー開発 1G • 普段:SALONBOARD の技術刷新と横断的な技術支援 • 趣味:Web 動向を追う、漫画を読む まのさん 4 -
自己紹介(サポート講師) • 名前:水足 友紀(みずたり ともき)@txxm_443 • 株式会社リクルートに2023年新卒入社 • 所属: ◦
株式会社インディードリクルートテクノロジーズ ◦ Technology 3部 • 普段:FEからインフラに転生。Terraformと格闘 • 趣味:コーヒー、雑草、インテリア みずたりさん 5 -
TypeScript研修の目的 / ゴール • 型と友だちになる ◦ 型エラーは仕事を増やす敵ではなく、コードの不具合(将来の仕事)を減ら してくれる仲間であることを実感する • 型システムの知識へのインデックスを作る
◦ すべての内容を完璧に覚えなくて良い ◦ 後から必要になったときに「TS研修でやったあの内容だ」となって、その後 の調べ物がスムーズになってほしい 7 -
「それは既に知っているよ」という人 • スライドの最後にある型パズルでもやって遊んでいてください ◦ 演習もやらなくて大丈夫です ◦ Slackの進捗確認には「パス」でリアクションください • または、任意のタイミングで研修に戻っても大丈夫です •
もしパズルが解けたら、せっかくなので研修の最後にでも共有してほしいです 8 -
Alt JS:JavaScriptにコンパイルされる(2/3) • いろいろあるAlt JS ◦ Flow, CoffeeScript, Haxe, PureScript,
Reason… • ブラウザで動かせる言語は基本的にJSだけだから • 一時期はどのAlt JSが天下を取るかという状況だったが、最近はTSに軍配が上 がっている様子 Alt JSのリストの参考:https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS#javascript-extensions 12 -
Alt JS:JavaScriptにコンパイルされる(3/3) • 古いブラウザでも動くようにコンパイルすることもできます • Downlevelingといいます ◦ 例:async / awaitはそのままではIEでは動作しないが、TSが頑張ってコー
ドを変換してくれる • もちろん、「まったくDownlevelingしない」ということもできます ◦ 例:tsは型検査だけで、コンパイルはBabelなど別のツールに任せる 13 -
JSのスーパーセットとは • すべてのJSのコードは、文法的に有効なTSのコードでもあります ◦ 例:以下のようなJSのコードがあります const msgString = "hello world";
console.log("現在のメッセージは" + msgString.length + "文字です"); ◦ これをTSのPlaygroundにそのままコピペして動かせます • JSから地続きで学習できるというメリット • (JSの良くない部分を引き継ぐ場合もあるというデメリット) 15 -
ミニ演習1 const msgString = "hello world"; console.log("現在のメッセージは" + msgString.length +
"文字です"); 1. TS Playgroundを開きます 2. TS Config -> Type Checking -> strictが有効になっていることを確認します 3. TS Config -> JSXがNoneになっていることを確認します 4. 上記のコードを実際にTS Playgroundへコピペしてみてください ◦ 実行できますか ◦ コンパイルの結果、どのようなJSになりますか ▪ 右側の.JSタブから確認します 16 -
TS Playgroundのおすすめの設定 1. Pluginsタブ -> Format On Saveを有効化、リロード 2. Format
On Saveタブで以下を有効化 ◦ Enable format on save ◦ Prevent copy link on save 3. Command + sでフォーマットされる 17 -
JSは動的な検査を行う言語(1/2) • 実行するまでエラーがわからないのが辛い • 1行目を次のように書き間違えたとします const msgString = null; console.log("現在のメッセージは"
+ msgString.length + "文字です"); • 実行するとエラーになります TypeError: Cannot read properties of null (reading 'length') • 2行目の「msgString.length」を実行しようとしてエラーになりました 19 -
JSは動的な検査を行う言語(2/2) • または、次のように間違えたとします const msgString = 12345; console.log("現在のメッセージは" + msgString.length
+ "文字です"); • こちらは実行してもエラーが出ません 現在のメッセージはundefined文字です • そういう言語仕様なので 20 -
いやいや • 「そもそもmsgStringは文字列が入る変数なんだから、nullや数値を代入してい る1行目の時点でおかしいでしょ」 ◦ ……と、思いましたよね? ◦ msgStringという変数名から「この変数にはなんらかの文字列(文字列”型” の値)を入れるぞ」という作者の気持ちが伝わってきたからです •
TSの静的型検査器も同じようなことを検査してくれます ◦ 私達のかわりにTSが同じことをやってくれます 22 -
TSで書いてみる 型注釈:「msgは文字列が入る変数」 というのをTSに教えている const msg: string = null; console.log("現在のメッセージは" +
msg.length + "文字です"); • すると、静的型検査で怒られました • ソースコードを実行する前に、静的解析によってエラーを発見できました 23 -
ミニ演習2 const msg: string = 12345; console.log("現在のメッセージは" + msg.length +
"文字です"); • 上記のコードをPlaygroundにコピペしてみてください ◦ どのようなエラーが表示されますか • msgに適当な文字列を代入するようにして、エラーを解消してみてください • エラーを解消したプログラムはどのようなJSにコンパイルされますか ◦ 特に型注釈の部分はどうなりますか 24 -
ありがたみが薄い? • 今回のコードは簡単だったのでパッと見て不具合がわかった • しかしこれが何万行もあったら……? ◦ • ※実際、プロジェクトにおけるTSのコードは数万行になりうる TSは、プログラムの各部分を、それが計算する値の種類によって分類することに より、プログラムがある種の振る舞い(例:nullや数値の.lengthを参照すると
いった動作)を起こさないことを検査してくれる*1 • 人の目で見るより、TSにやってもらったほうが効率的 *1:Pierce, Benjamin C. 型システム入門 プログラミング言語と型の理論. 株式会社 オーム社, 2013.(以降TAPLの略称で参照)の p.1にある型システムの定義から一部表現を借りています 25 -
注意:動的な検査が望ましい場合もある • 複雑な静的型検査をやろうとすると ◦ 難しい型パズルを書くことになったり ▪ ◦ • そのコードを後の人が見てわからないということになったり そもそも型システムの表現力が足りず実現不可能だったり
それよりは動的に検査してしまって、素直にエラー画面などを表示したほうが簡 単という場合もあります • やりたいことに応じて適切な手段を取れるようになりましょう 26 -
型注釈 • TSに「ここは〜〜型ですよ」と教える方法 • いろいろなところに書けます const a: string = "hoge";
let b: string; var c: string; function greet(name: string): string { return "hello, " + name; } class MyClass { e: string = ""; } 28 -
しかし、わかりやすさのために型注釈を書くこともある // 自明なので注釈は必須でない const num: number = 123; // dataの型が自明でないので注釈がほしい*1
// 注釈があると、「長い処理」を書き間違ってしまったときにここでエラーが出る // 注釈を消して型推論に任せると、どこか別の場所でエラーが出てデバッグが大変になる かも const data = hogeArray.map(/* 長い処理 */).filter(/* 長い処理 */).reduce(/* さらにつづく... *1:最近のエディタの中には、推論された型をinlay hintsによって表示してくれるものもある 30 -
基本的な型 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; 参考:https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#built-in-types 31 -
リテラル型 • 特定の値しか代入できない型 • 数値リテラル型 / 文字列リテラル型 / booleanリテラル型 const
oneHundred: 100 = 100; const helloMsg: "hello world" = "hello world"; const trueVal: true = true; const falseVal: false = false; 参考:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types 32 -
オブジェクトリテラル型 特定のプロパティを持ったオブジェクトを表す • ◦ よく使います const obj: { num: number;
str: string } = { num: 12345, str: "piyo", }; • 下線は型を表す部分です。それ以外はプログラムを表す部分です。 34 -
配列型 const array1: number[] = [1, 2, 3]; // または
const array2: Array<number> = [1, 2, 3]; • タプル型:Array型の一種 ◦ 各要素の型、順番、個数を正確に把握している型 const array3: [number, string, true] = [123, "hello", true]; 35 -
関数型(1/3) • 型は次のように表される (num: number, bool: boolean) => string //
引数がない時 () => string // 戻り値がない時 (num: number, bool: boolean) => void 36 -
関数型(2/3) • 型注釈として書くと以下のようになります。 const fn1: (num: number, bool: boolean) =>
string = (num, bool) => `${num}${bool}`; 37 -
関数型(3/3) • 同じ意味のコードを、アロー関数やfunction宣言へ型注釈することで書くことも できます。 const fn2 = (num: number, bool:
boolean): string => `${num}${bool}`; function fn3(num: number, bool: boolean): string { return `${num}${bool}`; } • その場の状況で、一番簡単に書ける記法を選ぶと良いと思います。 38 -
any型(1/3) • どのような値も代入できる const any1: any = 12345; const any2:
any = "hogehoge"; const any3: any = (a) => a; 39 -
any型(2/3) • ほとんどの操作(例:呼び出す、プロパティにアクセスする、被演算子にする) ができる(!) • // 静的型検査でおこられない const msg: any
= null; // 実行時エラーになる console.log("現在のメッセージは" + msg.length + "文字です"); • したがって慎重に使うべきです ◦ コードレビューなどでは相応の理由が求められることも 40 -
any型(3/3) • anyあるある ◦ 既存のJSプロジェクトにTSを導入している。とりあえず導入だけ済ませ て、型は後から書きたい ◦ JSのライブラリをTSで使いたいが、型定義が不足している。自分で定義を 書くのは時間がかかるのでいったんanyにしておきたい ◦
• 複雑な型エラーが出ていてよくわからん!詳しい人、後で助けてくれ!!! うまく使えばエスケープハッチとして役立つ ◦ しかし「よくわからないから」で放置してしまうと……技術的な負債として 積み上がってしまいます。 41 -
型エイリアス • 型に別名を付けられる*1 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 }; • 慣例として、型名は大文字から始める場合が多いです *1:型演算子や再帰型でもこの記法を使用するため、厳密にはただの別名ではないです。 42 -
演習について • 各問題には難易度が付いています ◦ ★:かんたん ◦ ★★:すこし難しい ◦ ★★★:難しい •
すべての問題を解く必要はありません • 隣同士との相談を推奨します • 講師を気軽に呼んでください • 答えを見て構いません(この資料の最後に掲載しています) 43 -
演習1 問題1[★]:次のuser変数に型注釈を付けてください • const user = { firstName: "太郎", lastName:
"佐藤", age: 24, favoriteFoods: ["寿司", "ラーメン", "カレー"], hasProgrammingExperience: true, }; 44 -
演習1 • 問題2[★★]:次のようなgetSelfIntroduction関数を書いてみてください ◦ 引数:1つ。問題1のuserオブジェクト。引数名は任意 ◦ 戻り値:string ▪ 次のような自己紹介文。[ ]の中は引数によって変化する部分
▪ 私の名前は[姓][名]です。年齢は[年齢]歳です。好きな食べ物は[〜〜 と〜〜と〜〜(〜〜には好きな食べ物配列の各要素が入る。要素数だけ 繰り返す)]です。プログラミングの経験[があります or はありません] ◦ • ヒント:Array.prototype.join()が役に立つはずです 問題3[★]:getSelfIntroduction関数を実際に実行し、consoleに自己紹介文を 出力してみてください 45 -
Class interface Flyable { fly(): void; } class Bird implements
Flyable { name: string; constructor(name: string) { this.name = name; } fly() { console.log(`${this.name}「パタパタ」`); } } const crow: Bird = new Bird("カラス"); インターフェースの例の出典: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) 47 -
Interface vs 型エイリアス(1/2) • どちらの記法もオブジェクトの型を定義できる interface Obj1 { type Obj2
= { prop: 123; prop: 123; } }; const obj1: Obj1 = { prop: 123 }; const obj2: Obj2 = { prop: 123 }; 他の細かい違いは:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces 49 -
Interface vs 型エイリアス(2/2) • わりとその場のノリで使い分けています • Interfaceは拡張される。型エイリアスはされない(エラーになる interface Hoge {
a: number; interface Hoge { a: number; どっちも同じ } b: string; interface Hoge { } b: string; } • ライブラリの型がinterfaceで書かれており、ユーザー側でそれを拡張する……み たいな場面もたまにある 他の細かい違いは:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces 50 -
Union • 「または」の意味 • 合併型*1、バリアント型*2、和型*3とも type NumOrStr = string |
number; type MaybeStr = string | null | undefined; • 文字列リテラル型と併用すると、列挙型のように扱うこともできる type Colors = "red" | "blue" | "green" | "yellow"; • (これとは別にenum構文もありますが、あまり利用されません) *1:Unionと同じ意味。*2:タグ付きのような「互いに素」なUnionを特に指す。*3:2つの型に対するバリアント型をいう。TAPL, 11.10節, p.108より 51 -
オプショナルプロパティ • JavaScriptのプログラムでよくあるバグは、nullまたは未定義の変数に対して、 値が存在する前提でアクセスしてしまうケース • (余談)Tony Hoareいわく「Null参照は最大の誤りだった。コンパイルですべて チェックできるようにしたかった」 ◦ •
Null参照:10億ドルの間違い strictNullChecks ◦ nullまたはundefinedを厳密に扱ってくれるTSのオプション ◦ 例えばundefinedになりうるオブジェクトのプロパティを参照しようとする と警告してくれます 52 -
「?」を使って「undefinedになりうる」ことを伝える • interface Hoge { a?: number; } type Fuga
= { b?: string; }; function fn(c?: boolean) { return c; } 53 -
他の表現 • Unionを使うと同じようなことが実現できます*1 type maybeNumber = number | undefined; •
「?」記法の方が短く簡単 *1:厳密には、プロパティが省略可能であることとundefinedになり得ることは違います。「?」記法は、場合によっては安全でないコードが書けてしまうため、undefinedとのUnionを使用すべきという意見もあります。プロジェクトの方針を確認しましょう。 54 -
型の絞り込み(1/2) • Union型は、if文やswitch文によって値をチェックしていくことで、より詳細な 型へ絞り込まれます function fn(a?: number) { // ここでaはnumber
| undefined型。以下はおこられる Math.sqrt(a); if (a === undefined) return; // ここではnumber型。以下はおこられない Math.sqrt(a); } 55 -
型の絞り込み(2/2) • • 他にも以下のような構文で絞り込まれます ◦ typeof x === “string” ◦
x instanceof HogeClass ◦ リテラル型の同値確認( if (x === “some message”) ) ◦ obj.x や obj[“x”] や x in objによるプロパティの存在確認 上記だけでは十分でない場合、ユーザー定義の型ガードを使用する手もあります ◦ 詳しくは割愛。必要になったら調べましょう 56 -
unknown型 • どのような値も代入できる // すべてできる const unknown1: unknown = 123;
const unknown2: unknown = "str"; const unknown3: unknown = undefined; const unknown4: unknown = null; • 可能な操作は少ない ◦ 他の型に絞り込まないとなにもできない 57 -
unknown型の絞り込み type User = { name: string }; // User型が与えられれば名前の文字数を返す。そうでなければundefinedを返す
function countMaybeUserName(maybeUser: unknown): number | 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; // maybeUser.nameはstring型 return maybeUser.name.length; } 58 -
never型(1/2) • unknownの双対 • どのような値も代入できない // すべて型エラー const never1: never
= 123; const never2: never = "str"; const never3: never = undefined; const never4: never = null; • ほとんどの操作ができる 59 -
never型(2/2) • 処理の上で、到達不能な部分などで現れる const func = (ab: "a" | "b")
=> { if (ab === "a" || ab === "b") return; console.log(ab); // abはnever型 }; const neverReturn = () => { throw "error!"; }; const returnedValue = neverReturn(); // returnedValueはnever型 60 -
unknown、never、any(1/2) • 値の種類が減ると、可能な操作が増える*1 値の種類 可能な操作 人間型 多:すべての人間 少:考える、歩く…… ITエンジニア型 中:すべてのITエンジニア
中: • 考える、歩く…… • アルゴリズムを考える FEエンジニア型 少:すべてのFEエンジニア 多: • 考える、歩く…… • アルゴリズムを考える • HTMLを書く *1:「外延と内包は反比例/逆比例(的に増減)する」などと言われる 61 -
unknown、never、any(2/2) • 値の種類が減ると、可能な操作が増える 値の種類 可能な操作 unknown型 多:すべての値 少:ほとんど不可能 never型 少:なし
多:ほとんど可能 any型 多:すべての値 多:なんでも可能 • unknown型とnever型はこれまでの原則にしたがっているが、any型は違う • any型は、値の種類が多い(それが何かわからない)のに、可能な操作も多い (なんでもできる)ので危険とされる 62 -
readonly 書き換え操作を型検査で抑止します • ◦ ランタイムのJSからは書き換え可能 type Obj = { readonly
prop: string; }; const obj: Obj = { prop: "hello", }; obj.prop = "goodbye"; // おこられる 63 -
ジェネリクス(2/4) • 引数をそのまま返すid関数を作りたいとします const id = (arg) => arg; •
すべての型に対してid関数のバリエーションを作るのは面倒です const idBoolean = (arg: boolean): boolean => arg; const idNumber = (arg: number): number => arg; const idString = (arg: string): string => arg; // ... 65 -
ジェネリクス(3/4) • unknownと型を付けて実装をまとめても良いですが、戻り値の型もunknownに なってしまいます const id = (arg: unknown): unknown
=> arg; const result = id(123); // resultはunknown型。ここからnumber型に絞り込むのは面倒 66 -
ジェネリクス(4/4) • ジェネリクスを使うと、以下のようにできます // 型変数Tで引数の型をキャプチャ const id = <T>(arg: T):
T => arg; const result = id<number>(123); // resultはnumber型 // または型推論に任せて const result = id(123); 67 -
演習2 • 問題1[★★]:以下のprintSelfIntroduction関数で使用されているSubject型を 書いてみてください。関数の仕様は次ページにあります function printSelfIntroduction(subject: Subject) { switch (subject.kind)
{ case "name": console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; } } 68 -
演習2 • 引数 ◦ • subject: 自己紹介の主題。kindとpayloadの2つのプロパティをもつ ▪ kind: ”name”または”favorite_food”
▪ payload: • kindが”name”の場合: 名前の文字列 • kindが”favorite_food”の場合: 好きな食べ物の文字列の配列 戻り値 ◦ なし 69 -
演習2 • 問題3[★★]:以下は、入力されたメッセージを大文字に変換するJavaScriptの プログラムです。このプログラムをTypeScriptに移植してください。 • 移植の際に型エラーが見つかった場合は、実行時エラーにならないようにコード を修正してください。修正方法は各自で自由に決めて良いです const msg =
prompt("入力されたメッセージを大文字に変換します"); alert(msg.toUpperCase()); 71 -
演習2 • 問題4[★]:ジェネリクスを使って、以下のようなtriple関数を作成してください const result1 = triple(123); // result1は[number, number,
number]型 const result2 = triple("hello"); // result2は[string, string, string]型 72 -
演習2 • 問題5[★★]:以下のように動作するmyArrayMap関数を定義してください。 • myArrayMapは、第1引数の配列を、第2引数の関数でマップした新しい配列を返 します。 const booleanArray = myArrayMap([0,
1, 2, 3, 4], (num) => num % 2 === 0); // booleanArrayはboolean[]型 console.log(booleanArray); // [true, false, true, false, true] const stringArray = myArrayMap([123, true, "text"], (elem) => elem.toString()); // stringArrayはstring[]型 console.log(stringArray); // ["123", "true", "text"] 73 -
部分型関係 • 2つの型に対する関係 • 「S型の値」を「T型の値」として扱っても安全であるとき、*1 • ◦ SはTの部分型(サブタイプ) ◦ TはSの上位型(スーパータイプ)
◦ S ⊆ Tと書く*2 is-a関係や、サブタイプ・ポリモーフィズムとも呼ばれる 参考: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:一般的には「<:」という記号が用いられます。本資料では簡単のために部分集合意味論をベースとして、このような表記にしました。より 高度な型システムでは、このような集合による直感は難しくなる場合があります。 74 -
Animal、Bird、Fish type Animal = { name: string }; type Bird
= { name: string; wings: "つばさ" }; type Fish = { name: string; scales: "うろこ" }; • nameプロパティは共通 • wingsやscalesプロパティは固有 75 -
「Bird型の値」を「Animal型の値」として扱っても安全 (鳥は動物) type Animal = { name: string }; type
Bird = { name: string; wings: "つばさ" }; const bird: Bird = { name: "スズメ", wings: "つばさ" }; const animal: Animal = bird; // できる console.log(`この動物は${animal.name}です`); // エラーにならない 76 -
「Bird型の値」を「Animal型の値」として扱っても安全 (鳥は動物) type Animal = { name: string }; type
Bird = { name: string; wings: "つばさ" }; const bird: Bird = { name: "スズメ", wings: "つばさ" }; const animal: Animal = bird; // できる console.log(`この動物は${animal.name}です`); // エラーにならない animal変数に代入されたオブジェクトは、今後はAnimal型に許される操作 (例:nameプロパティにアクセスする)しかできない →安全 77 -
「Bird型の値」を「Animal型の値」として扱っても安全 (鳥は動物) type Animal = { name: string }; type
Bird = { name: string; wings: "つばさ" }; Bird ⊆ const bird: Bird = { name: "スズメ", wings: "つばさ" }; Animal const animal: Animal = bird; // できる console.log(`この動物は${animal.name}です`); // エラーにならない 78 -
「Animal型の値」は「Fish型の値」として扱えない (動物は魚ではない) type Animal = { name: string }; type
Fish = { name: string; scales: "うろこ" }; const animal: Animal = { name: "イヌ" }; const printFish = (fish: Fish) => console.log(`この魚は${fish.name}です。${fish.scales}を持ちます`); printFish(animal); // エラー! 79 -
「Animal型の値」は「Fish型の値」として扱えない (動物は魚ではない) type Animal = { name: string }; type
Fish = { name: string; scales: "うろこ" }; animalをfishとして扱うと、 animalに存在しないscalesプロパティに const animal: Animal = { name: "イヌ" }; アクセスできてしまう const printFish = (fish: Fish) => console.log(`この魚は${fish.name}です。${fish.scales}を持ちます`); printFish(animal); // エラー! 80 -
「Animal型の値」は「Fish型の値」として扱えない (動物は魚ではない) type Animal = { name: string }; type
Fish = { name: string; scales: "うろこ" }; const animal: Animal = { name: "イヌ" }; const printFish = (fish: Fish) => console.log(`この魚は${fish.name}です。${fish.scales}を持ちます`); printFish(animal); // エラー! Animal ⊆ Fishではない 81 -
部分型関係(おさらい) • 「S型の値」を「T型の値」として扱っても安全であるとき、*1 ◦ SはTの部分型、TはSの上位型 ◦ S ⊆ Tと書く*2 •
Bird ⊆ Animal • Fish ⊆ Animal • Animal ⊆ Birdではない • Bird ⊆ Fishではない • 部分型関係は、変数への代入や、関数への引数の割り当てなど、プログラムの Animal Fish Bird 様々な箇所で検査される*3 参考: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:より正確には、割り当て可能関係が使用されています。 82 -
部分型関係の性質 • 反射的 ◦ Animal ⊆ Animal ◦ Bird ⊆
Bird ▪ • • Bird型の変数に、Bird型の値を代入できる 推移的*1 ◦ Pigeon ⊆ Bird ⊆ Animal ◦ ならば ◦ Pigeon ⊆ Animal Animal Bird Pigeon 前順序(pre-order)、擬順序とも 参考:TAPL 15.2節 p.142。*1:TSの代入可能関係は一部で推移的ではないようなのでご注意ください。詳細:https://github.com/sititou70/ts-extends-hierarchy。また、部分型関係と代入可能関係の違いについて 83 -
名前的と構造的 • Javaなどの型システムは名前的 ◦ 型には必ず名前がある ▪ ◦ 部分型関係は、ソースコード上に明示的に現れる(一目瞭然) ▪ •
例:class Animal { … 例:class Bird extends Animal { … TSの型システムは構造的 ◦ 名前の無い型を書ける ▪ ◦ 例:{name: string} 型 部分型関係は明示的ではなく、型の構造から決まる ▪ →さきほどは個別のプログラムを観察して安全/危険を判断したが、型の 構造から判定する規則がある 84 -
オブジェクトの部分型関係(1/2) {a: A; b: B} ⊆ {a: A} →部分型に余分なプロパティがあって良い (一部のプロパティを忘れても安全)
const bird: Bird = { name: "スズメ", wings: "つばさ" }; const animal: Animal = bird; // wingsプロパティを忘れる console.log(`この動物は${animal.name}です`); 85 -
オブジェクトの部分型関係(2/2) A1 ⊆ A2 ならば、{a: A1} ⊆ {a: A2} →共通するプロパティが部分型関係にあるなら安全*1
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}です`); *1:JSのオブジェクトは読み書き可能な参照のように振る舞うため、この規則は一般的には安全ではありません。しかし、TSは利便性を優先してこのような検査を行っていると考えられます。 86 -
オブジェクトの部分型関係(2/2) A1 ⊆ A2 ならば、{a: A1} ⊆ {a: A2} →共通するプロパティが部分型関係にあるなら安全*1
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}です`); 共通するプロパティresidentの型について、 Bird ⊆ Animalだから *1:JSのオブジェクトは読み書き可能な参照のように振る舞うため、この規則は一般的には安全ではありません。しかし、TSは利便性を優先してこのような検査を行っていると考えられます。 87 -
配列の部分型関係 A1 ⊆ A2 ならば、A1[] ⊆ A2[] →配列の中身が部分型関係にあるなら安全*1 const birdArray:
Bird[] = [{ name: "スズメ", wings: "つばさ" }]; const animalArray: Animal[] = birdArray; // Bird[] ⊆ Animal[] for (const animal of animalArray) { console.log(`この動物は${animal.name}です`); } *1:配列もまた、読み書き可能な参照のように振る舞うため、この規則は一般的には安全ではなく、TSは利便性を優先していると考えられます。詳しくは「TypeScriptにおける配列の共変性」を参照ください。 88 -
配列の部分型関係 A1 ⊆ A2 ならば、A1[] ⊆ A2[] →配列の中身が部分型関係にあるなら安全*1 const birdArray:
Bird[] = [{ name: "スズメ", wings: "つばさ" }]; const animalArray: Animal[] = birdArray; // Bird[] ⊆ Animal[] for (const animal of animalArray) { 配列の中身の型について、 Bird ⊆ Animalだから console.log(`この動物は${animal.name}です`); } *1:配列もまた、読み書き可能な参照のように振る舞うため、この規則は一般的には安全ではなく、TSは利便性を優先していると考えられます。詳しくは「TypeScriptにおける配列の共変性」を参照ください。 89 -
関数の部分型関係 • printBirdは、Birdを受け取る let printBird = (bird: Bird) => console.log(bird.name);
const bird: Bird = { name: "スズメ", wings: "つばさ" }; printBird(bird); 90 -
関数の部分型関係 • printAnimalをprintBirdとして扱っても安全 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); 91 -
関数の部分型関係 • printAnimalをprintBirdとして扱っても安全 let printBird = (bird: Bird) => console.log(bird.name);
const printAnimal = (animal: Animal) => console.log(animal.name); printBird = printAnimal; (animal: Animal) => void の値を (bird: Bird) => void の値として扱っても安全 const bird: Bird = { name: "スズメ", wings: "つばさ" }; printBird(bird); 92 -
関数の部分型関係 • getAnimalは、Animalを返す let getAnimal = (): Animal => ({
name: "スズメ" }); const animal: Animal = getAnimal(); console.log(`動物の名前: ${animal.name}`); 93 -
関数の部分型関係 • getBirdをgetAnimalとして扱っても安全 let getAnimal = (): Animal => ({
name: "スズメ" }); const getBird = (): Bird => ({ name: "スズメ", wings: "つばさ" }); getAnimal = getBird; const animal: Animal = getAnimal(); console.log(`動物の名前: ${animal.name}`); 94 -
関数の部分型関係 • getBirdをgetAnimalとして扱っても安全 let getAnimal = (): Animal => ({
name: "スズメ" }); const getBird = (): Bird => ({ name: "スズメ", wings: "つばさ" }); getAnimal = getBird; () => Bird の値を () => Animal の値として扱っても安全 const animal: Animal = getAnimal(); console.log(`動物の名前: ${animal.name}`); 95 -
関数の部分型関係 引数について • (animal: Animal) => void を (bird: Bird)
=> void として扱っても安全 戻り値について • () => Bird を () => Animal として扱っても安全 96 -
関数の部分型関係 引数について • (animal: Animal) => void ⊆ (bird: Bird)
=> void 戻り値について • () => Bird ⊆ () => Animal 97 -
関数の部分型関係 引数について • (animal: Animal) => void • (a1: A1)
=> void ◦ ⊆ ⊆ (bird: Bird) => void (a2: A2) => void A2 ⊆ A1が必要 戻り値について • () => Bird ⊆ () => Animal • () => R1 ⊆ () => R2 ◦ R1 ⊆ R2が必要 98 -
関数の部分型関係 引数について • (a1: A1) => void ◦ ⊆ (a2:
A2) => void A2 ⊆ A1が必要 戻り値について • () => R1 ◦ ⊆ () => R2 R1 ⊆ R2が必要 A2 ⊆ A1 かつ R1 ⊆ R2 ならば、 (a1: A1) => R1 ⊆ (a2: A2) => R2 99 -
部分型関係まとめ • 以下のようなエラーメッセージを見かけたら ◦ type '〜〜' is not assignable to
type '〜〜' • まずは部分型関係を思い出してみましょう • エラーが出ている部分について、部分型の値を上位型の値として扱っているか確 かめましょう {a: A; b: B} ⊆ {a: A} A1 ⊆ A2 ならば、A1[] ⊆ A2[] A1 ⊆ A2 ならば、{a: A1} ⊆ {a: A2} A2 ⊆ A1 かつ R1 ⊆ R2 ならば、 (a1: A1) => R1 ⊆ (a2: A2) => R2 105 -
型アサーション(as) • 値の型をキャストできます • 例*1 const myCanvas = document.getElementById("main_canvas") //
myCanvasはHTMLElement | null型 const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement; // myCanvasはHTMLCanvasElement型 *1:https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions より 106 -
アップキャスト • source as Targetのとき、Source ⊆ Targetであること • 例:bird as
Animal • 常に安全 ◦ Birdの値をAnimalとして扱っても何の問題もない • そもそも必要ないことも // as Animalは必要ない const animal: Animal = bird as Animal; 107 -
ダウンキャスト • source as Targetのとき、Target ⊆ Sourceであること • 例:animal as
Bird • 危険! • ◦ Animalの値をBirdとして扱う ◦ Birdにしかないプロパティにアクセスしたら実行時エラーになる 先程の例のように、相当の理由と自信がある場合のみ使ってください const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement; 108 -
部分型関係にない型へのキャストは認められない • これは認められない ◦ • bird as Fish しかし、いったん上位型を経由することでキャストできてしまいます //
これはできる bird as Animal as Fish // またはより一般的に bird as any as Fish bird as unknown as Fish 109 -
もちろんこれもできる bird as unknown as HTMLCanvasElement • めちゃくちゃです • この「いったん上位型を経由して部分型関係にない型にキャストすること」は
「愚かなキャスト」とも呼ばれます*1 • ちなみにJavaでは「ClassCastException」として認められません *1:一部の界隈でだけかもしれません。Featherweight Java、TAPL 19.4節より 110 -
Intersection(1/2) • 「かつ」の意味 • Unionの双対 • 交差型とも type A =
number & unknown; // Aはnumber型 type B = number & never; // Bはnever型 111 -
Intersection(2/2) type C = { a: number } & {
b: string }; // C ⊆ { a: number } かつ C ⊆ { b: string }であるようなCを考える*1 // したがって、 // Cは{ a: number; b: string }型 *1:S-Inter3規則、TAPL p.161 112 -
演習3 • 問題1[★]:以下のnumLiteral変数とnum変数に代入できる値にはどのようなも のがあるでしょうか。部分型関係を意識しながら複数挙げてみてください。 const numLiteral: 123 = ここに書ける値は?; const
num: number = ここに書ける値は?; const numArray: number[] = ここに書ける値は?; 113 -
演習3 • 問題3[★★★]: ◦ filterBirdsは、Birdの配列をフィルタリングする関数です。 ▪ ▪ ◦ 引数: •
birds:Birdの配列 • shouldExtract:birdsの各要素を取り出すかを判定する関数 戻り値:フィルタリングされたBirdの配列 filterBirdsに渡せるshouldExtractの実装を、次ページに示すサンプルコードの他 に2つ挙げてください ▪ 各実装でshouldExtractの型は異なる必要があります ▪ 実行時にJSのエラーになってはいけません ▪ filterの結果は変わっても良いです 115 -
演習3 type Animal = { name: string }; const filterBirds
= ( type Bird = { name: string; wings: "つばさ" }; birds: Bird[], shouldExtract: const birds: Bird[] = [ { name: "スズメ", wings: "つばさ" }, (bird: Bird) => boolean ): Bird[] => { { name: "カラス", wings: "つばさ" }, const filteredBirds: Bird[] = []; ]; for (const bird of birds) const shouldExtract = (bird: Bird): boolean => if (shouldExtract(bird)) bird.name === "スズメ"; filteredBirds.push(bird); console.log(filterBirds(birds, shouldExtract)); return filteredBirds; }; 116 -
演習3 • 問題4[★]:以下はそれぞれどのような型でしょうか。確かめてみてください type D = { prop: { a:
number } } & { prop: { b: string } }; type E = number & 12345; type F = "Love" & "Peace"; 117 -
発展:Utility Types • 型の取り回しを便利にする型 • 型を受け取って型を返します*1 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; } // まだまだある • たま〜〜〜に使います。今回は割愛 *1:型演算子とも。ただし高階の型演算子はTSにないです。工夫してそれっぽいものを実現することはできます。参考:https://github.com/gvergnaud/hotscript 119 -
余談:型パズル(1/5) • TSの型システムはチューリング完全であることが知られています ◦ • つまり、型検査が停止しないかもしれない(!) 実際には、処理系によって制限が設定されており、停止します ◦ ◦ が、それを回避する型ハックもあります
◦ 悪用すると型検査器を落とすこともできます(やめましょう) 120 -
余談:型パズル(2/5) • 型だけで様々なプログラムが書けます ◦ • コンパイル後のJSが空(”use strict”とかimport / exportとかしかない) 電卓:構文解析(トークナイザ、パーザ)、自然数の足し算と掛け算
◦ 出典:Yosuke Kurami、Yosuke Kurami(ネタ) TypeScript 型パズルで作る mini interpreter 121 -
余談:型パズル(5/5) • DOOM:1993年に発売さ れたゲーム(FPS) • 出典: MichiganTypeScript、 TypeScript types can
run DOOM • ※見やすいように、明度や コントラストを筆者が調整 124 -
型定義ファイルとは • TSコードのうち、型の定義に関する情報だけを抽出したもの • 拡張子は.d.ts • 例:「jQueryの型情報が表示されないんだけど、.d.tsファイル入れ忘れてね?」 ◦ • みたいによく言う
演習:適当な型定義ファイルを生成してみましょう ◦ Playgroundに適当なTSコードを貼り付けて、.D.TSタブを開いてみる 127 -
ライブラリの型定義はどこから来るのか(1/3) • 大きく3パターンあります • パターン1:パッケージに同梱されている ◦ ライブラリをnpm installした時点で型定義が使えるようになっています ◦ この手のやつは、パッケージのpackage.jsonに「“types”:
“index.d.ts”」と いった記載があります ◦ npmに以下のようなバッジがあります 128 -
ライブラリの型定義はどこから来るのか(3/3) • パターン3:自分で定義する ◦ Ambient module宣言という手法で自分でライブラリに型定義を与えること ができます ◦ • 少しマニアックなので今日は触れません
(パターン4:anyで妥協する) ◦ (これも重要な逃げ道) 130 -
型定義が壊れていたら • 提供された型定義が本体のAPIを網羅していないことも度々ある ◦ • e.g. 特定の関数の型定義がごっそり抜けてる / anyだらけ /
etc... コントリビューションチャンス! ◦ 型定義を修正するPRを出して世の中に貢献しましょう 131 -
モジュール • JSには複数のモジュールのスタイルがあります(歴史的事情) ◦ ES2015のモジュール ▪ 例:import hoge from “module-name”;
▪ ECMAScript 2015というバージョンで初めてモジュールに関する構文が仕様化され ました ▪ ◦ ◦ • それまではJSにモジュールという概念は無かった Common JSのモジュール ▪ 例:const hoge = require(“module-name”); ▪ ES2015以前から、Node.jsなどで使われていたスタイル 他にも色々 JSの実行環境によってモジュールのスタイルが異なるのが現状 ◦ したがって、利用するモジュールローダー(モジュールを読み込む仕組み)も使い分ける 必要があります 132 -
TSのモジュール事情 • 基本はESスタイルで記述 • コンパイル時にランタイムで利用したいモジュールローダーを指定すると、うまいこと 変換してくれます • • tsconfig.jsonのmodule:利用したいモジュールローダーを指定 ◦
commonjs:Node.jsで実行したい場合など ◦ esnext:ブラウザの<script type=“module”>を使う場合など ◦ 他の値も選択可能だが、使うケースは正直少ない 演習 ◦ importとexportを使って適当なTSのコードを書いてください ◦ Playgroundのmoduleの設定をcommonjsとesnextで切り替えたとき、生成される JSはどのように変化しますか 133 -
ビルド • JavaScriptのビルドの歴史は割と闇が深い • 太古の昔 ◦ AltJSが流行る前は、開発者が書いた.jsファイルをそのままHTMLの<script>タグで読み 込んでいました • よりリッチな体験がwebに求められるにしたがって、読み込まれるJSファイルは重厚長大化
• これにともなって、様々な課題が浮上 • ◦ JSファイルを分割したいが、<script>タグによる読み込み回数は増やしたくない ◦ 別言語(AltJS)で開発したい ◦ ダウンロードサイズ削減のため、JSファイルを圧縮したい ◦ etc… いろいろなツールが開発され、吟味され、混乱もあり、いろいろあって…… 134 -
モジュールバンドラという概念が生まれた • 以下のようなことをやってくれるツール ◦ importとexportを解析、必要な複数のJSファイルを1つにまとめる ◦ tree shaking:importしたけど利用していないライブラリをバンドル結果から除外 ◦ ソースマップ:元のコードとバンドル結果の対応関係を表すファイル.デバッグに
役立つ • ◦ バンドル結果の圧縮、難読化 ◦ Alt JSのコンパイル バンドラもいろいろある ◦ • webpack、rollup.js、Parcel、esbuild、Rolldown、Rspack、Turbopack…… 以前はwebpackが広く使われていたが、近年では新しいツールもいろいろある印象 135 -
最近だと • ECMAScriptのimport/export構文やブラウザの<script type=“module”>と いった標準化が進み、バンドラに頼らなくともモジュールが利用可能に • とはいえ、import の連鎖が深くなると、ブラウザが最後のモジュールに到達す るまでに時間がかかってしまいます ◦
この意味で、ファイルをまとめるモジュールバンドラは当分必要とされつづ けると思われます 136 -
周辺のツールチェイン:TypeScript自体の便利機能 • TypeScript本体に付属しているtscというツール自体にも便利機能があります ◦ --noEmit : JSの生成を行なわずに、型検査のみを実行するオプション. CI等 に組み込むと便利 ◦
--noUnusedLocals、--noUnusedParameters: 利用されていない変数を検知 してエラーにするオプション. 冗長なコードの削減になる • その他にも、tscには様々なオプションが用意されています。公式ドキュメントを 見てみると良いでしょう 138 -
周辺のツールチェイン:ESLint • AST(抽象構文木)を用いたコードのチェックツール。禁止したいコードパター ンをルールとして登録して利用します • • ルールの例 ◦ consoleを使わないこと ◦
再代入しない変数にはletではなくconstを用いること TypeScriptとESLintを併用する場合、 https://github.com/typescript-eslint/typescript-eslintを使います 139 -
周辺のツールチェイン:フォーマッター • ソースコードを機械的にフォーマットするためのツール • Prettier、Biome formatter、dprint、etc… • 下記のような不毛な議論に左右されなくなります • ◦
「文字列リテラルはシングルクォート or ダブルクォート」 ◦ 「セミコロンを文の末尾につけるべきか」 ファイルの保存時やコミット時に自動的にフォーマットが走る設定にしておくと 便利です ◦ ファイルの保存時:VSCodeの設定 ◦ コミット時:Git Hookの設定、例えばHuskyを使うなど 140 -
周辺のツールチェイン:自動生成等 • I/Oやネットワーク通信など、外部との境界ではanyやunknownが発生しがち ◦ 実際、外部システムとインターフェースの認識がずれていた、または認識は あっていたけど実装がずれていた、みたいな障害がありがちです • 使用する通信プロトコルに合わせて、IDL(インターフェース記述言語)から TypeScriptの型を生成するツールも多数存在します ◦
OpenAPI(Swagger), GraphQL, TypeORM, Prisma, etc… 141 -
まとめ:この資料では以下のことを学びました • • TSは ◦ Alt JS:JavaScriptにコンパイルされる ◦ (静的な)型が付いたJSのスーパーセット 型注釈、型推論、number、string、boolean、symbol、null、undefined、bigint、リテラ
ル型、オブジェクトリテラル型、配列型、タプル型、関数型、any、型エイリアス、 Class、Interface、Union、Optional、unknown、never、readonly、ジェネリクス、部分 型関係、型アサーション(as)、Intersection、Utility Types • 周辺には便利なツールがいっぱいある ◦ • 上手に使いこなしましょう 型は友達 142 -
参考文献 • TypeScriptの公式ドキュメント ◦ • https://www.typescriptlang.org/docs/ Pierce, Benjamin C. 型システム入門
プログラミング言語と型の理論. 株式会社 オーム社, 2013. ◦ TAPLの略称でも参照している 143 -
型パズルでもやって遊んでいてください(2/7) • (例題)Triple型[★]:指定された型が3つ入ったタプル型に評価されるTriple型 を定義してください。 type TestTriple1 = Triple<123>; // TestTriple1は[123,
123, 123]型 type TestTriple2 = Triple<"hello">; // TestTriple2は["hello", "hello", "hello"]型 // 回答例 type Triple<T> = [T, T, T]; 145 -
型パズルでもやって遊んでいてください(3/7) • If型[★★]:以下のようなIf型を定義してください type TestIf1 = If<true, 'a', 'b'> //
TestIf1は'a'型 type TestIf2 = If<false, 'a', 'b'> // TestIf2は'b'型 出典:https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md 146 -
型パズルでもやって遊んでいてください(4/7) • Increment型[★★★]:指定したnumberの次の数に評価される、Increment型を 定義してください。入力される数値の範囲は自身で決めて構いません type TestIncrement1 = Increment<1> // TestIncrement1は2型
type TestIncrement2 = Increment<5> // TestIncrement2は6型 147 -
型パズルでもやって遊んでいてください(6/7) • Mod3型[★★★★]:指定された数が3で割り切れるかを判定する、Mod3型を定 義してください。指定する数値の範囲は自身で決めて構いません type TestMod3_1 = Mod3<3> // TestMod3_1はtrue型
type TestMod3_2 = Mod3<4> // TestMod3_2はfalse型 149 -
型パズルでもやって遊んでいてください(7/7) • FizzBuzz型[★★★★]:指定した数未満のFizzBuzzの配列に評価される、 FizzBuzz型を定義してください。指定する数値の範囲は自身で決めて構いません type TestFizzBuzz1 = FizzBuzz<16> // TestFizzBuzz1は[1,
2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"]型 150 -
演習1、問題1の回答例 const user: { firstName: string; lastName: string; age: number;
favoriteFoods: string[]; hasProgrammingExperience: boolean; } = { firstName: "太郎", lastName: "佐藤", age: 24, favoriteFoods: ["寿司", "ラーメン", "カレー"], hasProgrammingExperience: true, }; 152 -
演習1、問題2の回答例 const getSelfIntroduction = (user: { firstName: string; lastName: string;
age: number; favoriteFoods: string[]; hasProgrammingExperience: boolean; }) => `私の名前は${user.lastName}${user.firstName}です。年齢は${ user.age }歳です。好きな食べ物は${user.favoriteFoods.join( "と" )}です。プログラミングの経験${ user.hasProgrammingExperience ? "があります" : "はありません" }`; 153 -
演習2、問題1の回答例 type Subject = | { kind: "name"; payload: string
} | { kind: "favorite_food"; payload: string[] }; 154 -
演習2、問題2の回答例 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; } } 155 -
演習2、問題2の回答例2 function printSelfIntroduction(subject: Subject) { switch (subject.kind) { case "name":
console.log(`私の名前は${subject.payload}です`); return; case "favorite_food": console.log(`私の好きな食べ物は${subject.payload.join("と")}です`); return; default: // satisfiesはTypeScript 4.9で追加された演算子で、[式] satisfies [型]のように書きます。 // [式]に[型]が付くことを検査しますが、[式]への型付けには影響しません。 // ここでは前半の検査の効果を利用しています。 // 詳細:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html subject satisfies never; } } 156 -
演習2、問題3の回答例 const msg = prompt("入力されたメッセージを大文字に変換します"); if (msg !== null) {
alert(msg.toUpperCase()); } else { alert("メッセージが入力されませんでした"); } 157 -
演習2、問題4の回答例 const triple = <T>(t: T): [T, T, T] =>
[t, t, t]; 158 -
演習2、問題5の回答例 const myArrayMap = <Elem, MappedElem>( array: Elem[], mapper: (elem:
Elem) => MappedElem, ): MappedElem[] => { const mappedArray: MappedElem[] = []; for (const elem of array) { mappedArray.push(mapper(elem)); } return mappedArray; }; 159 -
演習3、問題1の回答例 const numLiteral: 123 = 123; const numLiteral: 123 =
(() => { throw "hoge"; })(); const num: number = 123; const num: number = 12345; const num: number = (() => { throw "hoge"; })(); const numArray: number[] = [123, 12345, (() => { throw "hoge"; })()]; 160 -
演習3、問題2の回答例 const obj: { name: "動物" } = { name:
"動物" }; const obj: { name: string } = { name: "動物" }; const obj: { name: unknown } = { name: "動物" }; const obj: {} = { name: "動物" }; const obj: unknown = { name: "動物" }; const obj: any = { name: "動物" }; 161 -
演習3、問題3の回答例 • shouldExtractに渡せる関数を考えたい • ある関数をshouldExtractとして渡しても安全であるようにしたい • 「(S1) => S2 型の値」を「(bird:
Bird) => boolean 型の値」として扱っても安 全であるようにしたい ◦ 「ある関数」を「(S1) => S2 型の値」とおいた • (S1) => S2 ⊆ (bird: Bird) => boolean としたい • Bird ⊆ S1 かつ S2 ⊆ boolean を満たせば良い ◦ S1:Bird、Animal、unknownなど ◦ S2:boolean、true、false、neverなど 162 -
演習3、問題3の回答例 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) {} }; 163