Slide 1

Slide 1 text

僕が思い描く 勝手に TypeScriptの未来を 先取りする @yukukotani 2024/09/07 - Web Developer Conference 2024

Slide 2

Slide 2 text

自己紹介 小谷 優空 - @yukukotani ・VP of Technology @ Ubie, Inc. ・Maintainer of KumaUI ・Student @ Univ. Tsukuba

Slide 3

Slide 3 text

今日の趣旨 Call-this operatorが好きすぎて僕だけ勝手に使っている話を延々とします 自由研究とか図画工作の類です

Slide 4

Slide 4 text

Call-this operator、なんなんじゃそりゃ Function.prototype.call の糖衣構文となる二項演算子 TC39で提案中のStage-1プロポーザル NEW!

Slide 5

Slide 5 text

Function.prototype.call、なんなんじゃそりゃ 任意の値をthisに束縛して関数を呼べる → Call-this operatorだと できる レシーバをthisに束縛

Slide 6

Slide 6 text

Function.prototype.call、なんなんじゃそりゃ 任意の値をthisに束縛して関数を呼べる → Call-this operatorだと できる レシーバをthisに束縛 そんなの使ったことないよ

Slide 7

Slide 7 text

いろいろな Function.prototype.call G プロトタイプ汚染の防6 G 組み込み関数のモンキーパッQ G メソッドの動的切り替! G etc...

Slide 8

Slide 8 text

いろいろな Function.prototype.call G プロトタイプ汚染の防6 G 組み込み関数のモンキーパッQ G メソッドの動的切り替! G etc... 僕らはそんなことしないよ

Slide 9

Slide 9 text

では僕たちアプリケーション開発者 にとって何がうれしいのか?

Slide 10

Slide 10 text

(1) classの代替

Slide 11

Slide 11 text

よくあるclassベースのモデル 抽象クラスUserには2種2 G RegisteredUser, AnonymousUser 両方からリソースのURLを取れる RegisteredUserだけはメールを送れる

Slide 12

Slide 12 text

TypeScriptのclassには色々なつらみがある classのメソッドはシリアライズ/デシリアライズできない(当然) Next.jsのServer Actionsのような境界で壊れがち user2.getUrl is not a function

Slide 13

Slide 13 text

TypeScriptのclassには色々なつらみがある TypeScriptの構造的部分型ではclassの名前が無視される 全く同じプロパティを持っていたら別クラスでも型が一致してしまう https://example.com/articles/foo UserにArticle代入できちゃう Userと全く同じシグネチャ idとgetUrl

Slide 14

Slide 14 text

TypeScriptのclassには色々なつらみがある classのメソッドにはDead Code Eliminationが効かず、バンドルサイズ肥大化 メソッドが依存する外部ライブラリ等のTree Shakeも芋ずる式に効かなくなる このメソッドは未使用なのに ビルド成果物に残ってしまう

Slide 15

Slide 15 text

TypeScriptのclassには色々なつらみがある そもそもclass一般の話題として、継承によるモデリングの限界も見えてきている https://speakerdeck.com/naoya/guan-shu-xing-puroguramingutoxing-sisutemunomentarumoderu?slide=54

Slide 16

Slide 16 text

人々はtypeと関数に救いを求めた G 振る舞いは純粋な関数として実8 G データはただのtypeとして定& G Tagged Unionで多態相当の表現

Slide 17

Slide 17 text

type+関数が結論なのか!? データはただのオブジェクトなのでシリアライズ・デシリアライズできる エラーにならない!

Slide 18

Slide 18 text

type+関数が結論なのか!? Tagged Unionのようにタグを付けておけば異なる型をちゃんと区別できる 発展的には Branded Type のようなテクニックもある エラー!kindが異なるので代入できない

Slide 19

Slide 19 text

type+関数が結論なのか!? Dead Code Elimination も当たり前に効く 未使用なので関数丸ごと消える

Slide 20

Slide 20 text

type+関数が結論なのか!? いいことづくしなのにclassを使い続ける人が多いのはなんでだろう

Slide 21

Slide 21 text

classのここが嬉しい! データに対する振る舞いが閉包的に定まる 普通に書けばデータそのものが振る舞いの名前空間となるから(prototype) userに対する振る舞いは getUrl, sendEmailに定まる

Slide 22

Slide 22 text

classのここが嬉しい! より直接的に言うと、レシーバを書いた時点でその振る舞いがわかる

Slide 23

Slide 23 text

type+関数だとメンタルモデルが異なる 関数ベースだと、まずグローバルな名前空間から振る舞いを探す必要がある (名前空間のためにオブジェクト作ったりするとTree Shakeできなくなる)

Slide 24

Slide 24 text

それ、Call-this operator でできるよ! レシーバにしたい型を This Parameter で定義 普通の引数の代わりに thisの型を定義

Slide 25

Slide 25 text

それ、Call-this operator でできるよ! 型が名前空間として機能し、 データ→型→振る舞い という紐付きで同定できる ~> を打ったタイミングで レシーバとThis Parameterを型検査 マッチするものだけ取り出せる

Slide 26

Slide 26 text

DEMO

Slide 27

Slide 27 text

(2) Tree Shake可能なユーティリティ

Slide 28

Slide 28 text

僕たちも嬉しい Call-this operator メソッドチェーンのDXとツリーシェイク可能なインポートの両立 zodとvalibotの良いとこ取りみたいなことができる 体験は良いけど Tree Shaking効かない 補完体験を維持しつつ Tree Shakingも効く! Tree Shaking効くけど 補完体験が悪い

Slide 29

Slide 29 text

DEMO

Slide 30

Slide 30 text

よさそうなことはわかった 使いたい

Slide 31

Slide 31 text

Call-this operator、なんなんじゃそりゃ Function.prototype.call の糖衣構文となる二項演算子 TC39で提案中のStage-1プロポーザル NEW!

Slide 32

Slide 32 text

TC39? Stage 1? ECMAScriptの仕様を議論する委員会。仕様提案をStageに分けて管理している Stage 0 新しく提出されたプロポーザル。まだTC39の議論対象ではない Stage 1 Championと呼ばれる推進者が決まり詳細化されたプロポーザル。TC39の議論対象になる Stage 2 セマンティクスや文法といった仕様の草案が定義され、その方向性に合意したプロポーザル Stage 2.7 仕様が決定し、原則承認されたプロポーザル。実装上の不都合がないかテストやプロトタイピングで検証する Stage 3 本番での実装が推 奨されるプロポーザル。実 世界で不都合がないか フィー ドバック を集める Stage 4 正式に承認されたプロポーザル。 ここか ら変更は 入らず、 次バー ジョンの EC MAScrip tに 含まれる

Slide 33

Slide 33 text

Babelプラグインで試したい Stage3以前の構文系の提案はBabelプラグインで試せるのが一般" D @babel/plugin-proposal-record-and-tuple (Stage 2 D @babel/plugin-proposal-throw-expressions (Stage 2) @babel/plugin-proposal-throw-expressions

Slide 34

Slide 34 text

Babelプラグインじゃ足りなかった;; Babelはあくまでコードのトランスパイルだけやってくれる(当たり前) 今回は まで含めた開発体験を手触りしたい 補完や型検査

Slide 35

Slide 35 text

TypeScriptで試したい TypeScriptもまだブラウザで動かない新機能をサポートして ダウンレベルコンパイルしてくれる Explicit Resource Managementとか

Slide 36

Slide 36 text

TypeScriptにはまだ入らない;; TypeScriptはStage 3以降の提案のみを実装するポリシー Call-this operatorはまだStage 1 https://github.com/microsoft/TypeScript/blob/a709f989/src/lib/README.md

Slide 37

Slide 37 text

Stage 3に進めてもらい・・・たい・・・ https://github.com/tc39/proposal-call-this プロポーザルの最終コミットが1年前、tc39/agendas でも音沙汰なし

Slide 38

Slide 38 text

J.S. Choiさんが忙しそう Call-this operatorとかPipeline operatorのチャンピオン 本業は内科医なのにtc39とかUnicode Consortiumとかやってる謎の人 https://github.com/js-choi

Slide 39

Slide 39 text

標準化は待たずに 自由研究するしかない!

Slide 40

Slide 40 text

TypeScriptコンパイラを雰囲気で理解する https://github.com/microsoft/TypeScript-Compiler-Notes/tree/314256/intro 公式ガイドっぽいのがあったので斜め読み microsoft/Typescript-Compiler-Note

Slide 41

Slide 41 text

TypeScriptコンパイラを雰囲気で理解する https://github.com/microsoft/TypeScript-Compiler-Notes/tree/314256/intro 公式ガイドっぽいのがあったので斜め読み microsoft/Typescript-Compiler-Note

Slide 42

Slide 42 text

トークナイザの実装 入力したコードを意味のある単位で区切った配列にする 今回は新しいトークン `~>` が登場するので対応が必要 NEW!

Slide 43

Slide 43 text

トークナイザの実装 作戦:似たようなトークンを見つけて追いかける ./src/compiler/types.ts

Slide 44

Slide 44 text

トークナイザの実装 雰囲気でトークンを定義する ./src/compiler/types.ts ./src/compiler/scanner.ts

Slide 45

Slide 45 text

トークナイザの実装 すでにチルダをconsumeしてるところがあったので雰囲気で付け加える ./src/compiler/scanner.ts

Slide 46

Slide 46 text

トークナイザの実装 それっぽくトークナイズできた!

Slide 47

Slide 47 text

TypeScriptコンパイラを雰囲気で理解する https://github.com/microsoft/TypeScript-Compiler-Notes/tree/314256/intro 公式ガイドっぽいのがあったので斜め読み microsoft/Typescript-Compiler-Note

Slide 48

Slide 48 text

パーサの実装 トークン列を木構造(AST)に変換する

Slide 49

Slide 49 text

ASTを考える パーサーが吐くAST構造を考えるために、既存の似ている構文を観察する globalThis.window.closeまではPropertyAccessExpressionで それを呼び出すとCallExpressionで包まれる

Slide 50

Slide 50 text

ASTを考える Call-this expressionも同じ構造でいけるか? → `~>` があるなら () で呼び出さないと構文エラーなのでちょっと違う。

Slide 51

Slide 51 text

ASTを考える レシーバから呼び出し部分まで1つのノードに入れて、 PropertyAccessExpressionとCallExpressionの合いの子みたいにする receiver expression typeArguments arguments

Slide 52

Slide 52 text

パーサの実装 parser.ts から実装箇所を探したいが、1万行あるのでエスパーが必須 → 仮説: `expr.name` のピリオド解析時に PropertyAccessExpression を     作っているところに相乗りできないか?

Slide 53

Slide 53 text

パーサの実装 それっぽい関数があった。`expr.name` の `.name` の部分を解析していそう ここから親にたどってみる

Slide 54

Slide 54 text

パーサの実装 `expr.name?.foo.bar` みたいなチェーンを再帰的に解析している関数があった ここに相乗りできそう!

Slide 55

Slide 55 text

パーサの実装 `~>` があったら CallThisExpressionとして解析するようにして完了

Slide 56

Slide 56 text

TypeScriptコンパイラを雰囲気で理解する https://github.com/microsoft/TypeScript-Compiler-Notes/tree/314256/intro 公式ガイドっぽいのがあったので斜め読み microsoft/Typescript-Compiler-Note

Slide 57

Slide 57 text

エミッタの実装 ASTをJavaScriptとして出力する

Slide 58

Slide 58 text

エミッタの実装 CallThisExpressionノードの子ノードを順番に出力していくだけ

Slide 59

Slide 59 text

嬉しいですね 入力したTypeScriptを解釈してJavaScriptに出力するところまで一通り動いた!

Slide 60

Slide 60 text

嬉しくない Call-this parameterを解釈できるJavaScriptランタイムはたぶんない(それはそう)

Slide 61

Slide 61 text

トランスフォーマの実装 ./src/transformers 配下に target ごとの実装があり、 visitor pattern で変換している 今回は esnext.ts に足していく

Slide 62

Slide 62 text

トランスフォーマの実装 `receiver.fn(arg)` を `fn.call(receiver, arg)` に変換。 既に色々なfactoryがあるので組み立てるだけ

Slide 63

Slide 63 text

嬉しいですね 一般的なランタイムで動くJavaScriptを出せた!

Slide 64

Slide 64 text

嬉しくない せっかくTypeScriptなのにぶっ壊せちゃう

Slide 65

Slide 65 text

型チェッカの実装 5万行のクソデカ型チェッカ checker.ts をどうにかする This parameterとレシーバの型の一致をチェックする以外は 普通の関数呼び出しと同じなのでパクって実装する 変更でかいので気になる人はコミット見てください(雑) https://github.com/microsoft/TypeScript/pull/58294/commits/ ce12997edd2302325e1463ec1554ae006d8ec3b3

Slide 66

Slide 66 text

嬉しいですね レシーバの型エラーが出るようになった

Slide 67

Slide 67 text

補完も効かせたい VSCodeのTypeScript ExtensionからTS Serverに補完依頼がくる VSCode TS Server `>` で補完だしたいのだが こちらのリストでございます!

Slide 68

Slide 68 text

補完も効かせたい まずExtension側で `>` でも補完を呼び出すようにする TypeScript Extensionは特別にVSCodeに組み込まれているので microsoft/vscode に手を入れる ./ extensions/typescript-language-features/src/languageFeatures/completions.ts NEW!

Slide 69

Slide 69 text

補完も効かせたい TypeScript (TS Server) 側ではまず `>` が Call-this なのか確かめる じゃないと普通の比較演算とかでも補完がでて困っちゃう

Slide 70

Slide 70 text

補完も効かせたい 補完リストは以下のロジッR まずグローバルな関数呼び出しと同様のをリストアッ( その中からレシーバとThis Parameterが一致する関数に絞る

Slide 71

Slide 71 text

嬉しいですね 補完が効きます

Slide 72

Slide 72 text

DEMO

Slide 73

Slide 73 text

まとめ 5 Call-this operator が来るととても嬉しいで8 5 TypeScriptに変更を加えると新規機能が実装できて楽しいです

Slide 74

Slide 74 text

ありがとうございました