Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TypeScript、上達の瞬間

sadnessOjisan
November 16, 2024

 TypeScript、上達の瞬間

tl;dr
焼肉

sadnessOjisan

November 16, 2024
Tweet

More Decks by sadnessOjisan

Other Decks in Technology

Transcript

  1. MinCaml の存在を教わる • 東京大学での CPU 実験で使わ れる教材の存在を教えてもらう • プロセッサとコンパイラを実験 する課題の中で、OCaml

    で OCaml を実装する場面がある らしい • 教材が一般公開されていて僕た ちも遊べる • https://esumii.github.io/min- caml/
  2. MinCaml 以外の選択肢 字句解析 構文解析 型検査 最適化 アセンブリの 出力 Go言語でつくるインタプリタ •

    MinCaml の最初の壁の構文解析ま でがかなり丁寧 • MinCamlではパーサージェネレー タを使うが、こちらは手書きする • つまり、ナイーブで分かりやすい Goで学ぶはインタプリタ形式 なのでアセンブリは出力しな い。コンパイラ本もあるらし い
  3. MinCaml 以外の選択肢 字句解析 構文解析 型検査 最適化 アセンブリの 出力 TaPL •

    項や規則の定義をして、それを実 際に実装する課題があり、理論と 実装の橋渡しが見えてくる • 型システムにいろんな機能を増や していく進み方で学べる • 挫折するが、輪読会などに行くと 学べる(tapl.ts)
  4. MinCaml 以外の選択肢 字句解析 構文解析 型検査 最適化 アセンブリの 出力 ゼロから学ぶRust •

    デバッガやシェルを実装させるどう考えても入門 書ではない入門書 • RustでRustのサブセットを実装する章がある • Rustはアフィン型やライフタイムという機能が あり、変数の消費を型で検査できる(1度使うと 使えなくなる) • 型の世界でライフタイムを表現し、HM型推論を 実装する演習を通して、型検査に独自機能を足す と言うことが学べる
  5. TypeScript 学習の壁 1. なぜ TypeScript が必要なのかが分からない a. TSなしでコードを書いてみてランタイムエラーをたくさん踏むと欲しくなる b. TSが保証しているようなことをテストなどで担保しようとすると、めんどくさくなって欲し

    くなる c. 形式手法など学問として存在している分野を学ぶと、トップダウンに理由を人に説明できる ようになる 2. TypeScript の恩恵を引き出す方法が分からない 3. 実用・運用に乗せる方法が分からない
  6. 型の上下関係を知り、any と向き合う • TS使い始め: any 使って型推論を通すぜうぇーい • 実行時エラー: もしかして any

    って悪いのか? • Twitterでは: any 撲滅! • 学び: any ってダメなのかも • 冷静になって: そもそも any ってなんですか? 後に引き継いだ同僚が600箇所の エラーを直すことになる。 (本当にごめんなさい)
  7. 型の上下関係 • 型の関係は supertype, subtype という上下関係で表現できる • 何でも受け入れる型: unknown •

    何でも受け入れない型: never • (上下関係の外側で) 両方の性質を持つ: any unknown never
  8. 変性 • Animal と Human というクラスがある • サブタイプはスーパータイプに代入できる • Animal

    animal = new Human() はできる • Animal[] animals = new Human[2] はでき るのか? • P1 が P2 のサブタイプである ⇒ P1[] が P2[] のサブタイプである? • Java でよく言われていた問題らしい is-a 動物 (という概念)
  9. 変性 • 共変: ある型 A が型 B のサブタイプであるとき、Container<A> が Container<B>

    のサブタイプとして扱える • 反変: A が B のサブタイプのとき、Handler<B> が Handler<A> のサブタイ プとして扱える • 他にも「共変かつ反変」や「共変でも反変でもない」というパターンも [🙀, 😱, 🙀]
  10. TypeScript の変性はどうなのか • 配列は共変 • Javaと同じ問題を踏み抜いている! • animals である [🙀,

    🙀, 🙀]を humans[😱, 😱, 😱]になるように破壊しても型検査が怒 らない • 何が問題か -> 配列を代入しても参照がコ ピーされるだけ。animalsの要素を入れ替え ると、humansの要素も書き換わり、human 専用メソッドを呼び出そうとすると、ラン タイムエラー • と言う理由で FlowType を薦められていた 過去がある https://qiita.com/na-o-ys/items/aa56d678cdf0de2bdd79
  11. Opaque • TypeScript では userId👦 と ramenId🍜 を区別できない • 構造的型付けでは、string

    という同じ構造 • flowtype にはそれらを区別できる opaque という機能がある ◦ https://flow.org/en/docs/types/opaque-types/
  12. exhaustive check • if や switch で分岐を全てやり切った後 は never になる

    • else 節や default 節で never かどうか を検査するようにすれば、分岐漏れをコ ンパイル時に気づける https://typescript-jp.gitbook.io/deep-dive/t ype-system/discriminated-unions
  13. TypeScript 学習の壁 1. なぜ TypeScript が必要なのかが分からない 2. TypeScript の恩恵を引き出す方法が分からない a.

    別言語のプラクティスや設計ミスを知る b. 自分が教えてもらったのは、Java、F#、OCaml 3. 実用・運用に乗せる方法が分からない
  14. ビルドを通せない • Error Cause を使いたい • Top Level Await を使いたい

    • JSON を import したい • import するときの拡張子には何を選んだらいいか分からない
  15. オプションを使いこなす • target: どのバージョンのJSに落とし込むか • lib: targetがサポートしていない機能を実装するためのポリフィルを実装に 埋め込んでくれる(Promise, Array.prototype.hoge, …

    • module: cjs, esm などのモジュール形式。TLA は ESM じゃないと動かない など、cjs だと同期的という制約があったりしてこのオプションをいじらな いとビルドできないものがある • esModuleIntertop: 使うライブラリや既存コードによっては cjs との互換性 を意識しないとビルドできなくなる • moduleResolution: ビルドツールが前提とする方法によって切り替える必要 が出てくる
  16. TypeScript 学習の壁 1. なぜ TypeScript が必要なのかが分からない 2. TypeScript の恩恵を引き出す方法が分からない 3.

    実用・運用に乗せる方法が分からない a. ビルドできない理由の6割はモジュールに対する理解だと思うので、ECMAScript そのもの を勉強する b. ECMAScript 仕様輪読会 などの勉強会に参加すると良いと思う。 優しい人が教えてくれるはず🟢
  17. MyPick<{hoge: number; fuga: string; piyo: number}, “hoge” | “fuga””> の結果を、{hoge:

    number; fuga: string } にしたい。 MyPick<A, B> で呼ぶとして、UnionTypeになるB は オブジェクトA のキーで構成されないといけない 制約の extends, キーを抽出する keyof を使う
  18. MyPick<{hoge: number; fuga: string; piyo: number}, “hoge” | “fuga””> の結果を、{hoge:

    number; fuga: string } にしたい。 MyPick<A, B> で呼ぶとして、A からキーBを構成する要素で、 オブジェクトを抽出する操作を繰り返したい 繰り返しの in を使う
  19. MyPick<{hoge: number; fuga: string; piyo: number}, “hoge” | “fuga””> の結果を、{hoge:

    number; fuga: string } にしたい。 MyPick<A, B> で呼ぶとして、in で B から hoge と fuga が抽出されて繰り返されるので、A から その key に該当する要素を繰り返しで抽出されるように [key in K]: T[key] とする
  20. https://github.com/type-challenges/type-challenges/issues/24969 Promise<U> から U を抽出したい Promise<Promise<U>> から U を抽出したい T

    は Promise であるべきなので、extends PromiseLike<any> Promise の入れ子を表現できていないが、入れ子の段階で繰り返しが必要であることと、 Collection(配列やオブジェクト)が対象でないことから Mapped Types を使えなさそうで、 再帰の可能性を視野に入れる。繰り返されることを考えると1重のPromiseで済みそうと予想
  21. 強く静的に型付けられた 関数型言語から学ぶ • 「強く静的に型づけられた関数型言語をやってみると新しい視点が手に入る よ」 • OCaml, Rust, Haskell, Scala

    を勉強する ◦ けど、難しすぎて独学はできないのでこれも手取り足取り教わった... 今思えば、TSにおける上達のコツのほとんどは、これらの言語を使って いると当たり前に身についていたものな気がしている
  22. 強く静的に型付けられた言語にある概念 を持ち込んでいるライブラリの例 • fp-ts (https://github.com/gcanti/fp-ts) ◦ 名前の通りのことを実現するためのあれこれ集 • option-t(https://github.com/option-t/option-t) ◦

    Result, Optional を持ち込む。Rust と同じIFになっているのが良い • monocle.ts(https://github.com/gcanti/monocle-ts) ◦ Scala に同名のライブラリがありそれのTS実装。Lens というデータ構造を使って、 immer.js がしていることを実現する
  23. Result 型の流行 • try catch をし忘れる, エラーハンドリ ングのし忘れを型検査で防ぎたい • 成功失敗を表現するオブジェクトで包

    めば良いが、呼び出す側が毎回 Result を解かないといけないのがめんどくさ い • Result という文脈を保ったまま計算す る方法を考える めんどくさい
  24. 文脈は型を引数に取る • Result, Option, List は bind(>>=) や map といった共通機能を持ち、文脈

    を他の文脈と組み合わせるためのインターフェースとなる • m a … 文脈m に包まれた a • (>>=) :: m a -> (a -> m b) -> m b (※mは文脈) bind 演算子と呼ぶことを知らなくて、ググれ なくて困った思い出
  25. 型注釈から意味を読み取る • m a -> (a -> m b) ->

    m b • 文脈に包まれた a を、a を引数にとって文脈に包まれた b を返すに渡すと、 文脈に包まれた b を返す • 文脈の中身が a から b に入れ替わっている • 文脈を保ったままの計算ができる • 文脈を一回解いて、関数を適用して、文脈で包みなしてくれる • どこかで見覚えが... -> flatMap • [1, 2, 3].flatMap(el => [el]) 👉 [1, 2, 3]
  26. 意味を読み取れると不適切な使われ方に も気づける • 失敗を表現する Result は、フォームの入力 エラー管理に使えそう? • m a

    -> (a -> m b) -> m b • Result<Data, Input1> -> (Input1 -> Result <Data, Input2>) • Input1 のエラー内容を忘れてしまっている • 入力が複数のものに Result は適さない ◦ Validated という構造で解決できる(Scala) https://blog.ojisan.io/monad-applicative/