Slide 1

Slide 1 text

Stage 2 Decorators の変遷 pixiv Inc. petamoriken 2021.5.7

Slide 2

Slide 2 text

2 自己紹介 ● moriken (petamoriken) ● 2017年5月入社(アルバイト) ● プロダクト支援本部課題解決部(福岡) ● JavaScript が好きで TC39 会議を追ってます ○ 「moriken scrapbox」で検索 ○ 2018 年あたりから大体の ESNext の議論の 流れが日本語で読めます!(最近サボり気味) petamoriken エンジニア

Slide 3

Slide 3 text

3 Ecma International TC39 ● JavaScript の言語仕様 ECMAScript を策定する専門委員会 ● 提案一覧は GitHub 上で管理されている ○ https://github.com/tc39/proposals ● だいたい2ヶ月ごとに会議を開いている ○ アジェンダ: https://github.com/tc39/agendas ○ 議事録: https://github.com/tc39/notes

Slide 4

Slide 4 text

4 ECMAScript の策定プロセス ● Stage 1 Proposal ○ TC39 の担当者が決まる ● Stage 2 Draft ○ 仕様草案が作られる ● Stage 3 Candidate ○ 仕様がほぼ確定し、レビューが完了する ● Stage 4 Finished (ES2022) ○ polyfill ではない2つの実装で問題がないことが確認される

Slide 5

Slide 5 text

5 Decorators とは ● 現状 Stage 2 Draft にある提案仕様(草案) ● 他の言語ではアノテーションと呼ばれたりする ● JavaScript のクラスを拡張するシンタックスの追加 ○ クラスに修飾する位置でおおまかに 3つに分類できる 👉 ● トランスパイルして既に広く使われている ○ NestJS や Angular など @classDecorator class Klass { @fieldDecorator prop = 1; @methodDecorator method() { } }

Slide 6

Slide 6 text

6 https://nestjs.com/

Slide 7

Slide 7 text

7 今から4つデコレーターの定義部分を見せるので どれが現在の仕様のものかをあててください ただしメソッドデコレーターとして使われるものとします 問題 class Klass { @logged method() { } }

Slide 8

Slide 8 text

// 1 function logged(target, name, descriptor) { const original = descriptor.value; descriptor.value = function wrapper(...args) { console.log(`called ${name}`); return original.call(this, ...args); }; return descriptor; } // 2 function logged(original, { name }) { return function wrapper(...args) { console.log(`called ${name}`); return original.call(this, ...args); }; } // 3 decorator @logged { @wrap((original) => { return function wrapper(...args) { console.log(`called ${original.name}`); return original.call(this, ...args); }; }) } // 4 function logged() { return { set(target, instance, name, original) { return function wrapper(...args) { console.log(`called ${name}`); return original.call(this, ...args); }; }, }; } 8

Slide 9

Slide 9 text

// 1. 2015 First Decorators function logged(target, name, descriptor) { const original = descriptor.value; descriptor.value = function wrapper(...args) { console.log(`called ${name}`); return original.call(this, ...args); }; return descriptor; } // 2. 2021 Current Decorators function logged(original, { name }) { return function wrapper(...args) { console.log(`called ${name}`); return original.call(this, ...args); }; } // 3. 2019 Static Decorators decorator @logged { @wrap((original) => { return function wrapper(...args) { console.log(`called ${original.name}`); return original.call(this, ...args); }; }) } // 4. 2020 Read/Write Trapping Decorators function logged() { return { set(target, instance, name, original) { return function wrapper(...args) { console.log(`called ${name}`); return original.call(this, ...args); }; }, }; } 9

Slide 10

Slide 10 text

提案仕様がころころと変わっていった 経緯を話していきます 10

Slide 11

Slide 11 text

11 2015 First Decorators ● 函数による定義 ○ 引数にクラス自身、プロパティのキー、プロパティディスクリプタ ○ プロパティディスクリプタを加工して返す function nonenumerable(target, key, descriptor) { descriptor.enumerable = false; return descriptor; } class Person { @nonenumerable get kidCount() { return this.children.length; } }

Slide 12

Slide 12 text

12 2015 First Decorators (問題点) ● Private Fields に対するフィールドデコレーターをどうするか function fieldDecorator(target, key, descriptor) { console.log(key); // ??? } class Klass { @fieldDecorator #x = 0; } 👉 Class Fields の提案にあわせてデコレーターの仕様も新しくしよう!

Slide 13

Slide 13 text

13 エコシステムへの影響 ● 現在広く使われているデコレーターはこの 2015 年の提案仕様のもの ○ TypeScript で experimentalDecorators フラグを付けたとき ○ Babel の @babel/plugin-proposal-decorators で legacy フラグを付けたとき ● MobX がこれを嫌ってデコレーターを使うのを辞めた How should I use decorators in transpilers today? Unfortunately, we're in the classic trap of, "The old thing is deprecated, and the new thing is not ready yet!" For now, best to keep using the old thing. https://github.com/tc39/proposal-decorators

Slide 14

Slide 14 text

14 2018 Descriptor-based Decorators ● 函数による定義 ○ 引数にただ1つのディスクリプタ(プロパティディスクリプタを含む) ○ ディスクリプタを加工して返す interface Descriptor { kind: "class" | "field" | "method"; elements?: Descriptor[]; // Class Decorators key?: string | symbol | PrivateName; // Field/Method Decorators descriptor?: PropertyDescriptor; // Field/Method Decorators initializer?: () => unknown; // Field Decorators ... }

Slide 15

Slide 15 text

15 2018 Descriptor-based Decorators (問題点) ● 仕様が複雑 ● 処理が重い/実行速度が遅い ○ いままでは Object.defineProperty 相当の機能だったのに対して、 今回のこの仕様ではなんでも出来すぎる ○ 最適化が困難 👉 なんでもできる表現力そのままにもっと速く実行できるようにしよう!

Slide 16

Slide 16 text

16 2019 Static Decorators ● 新しいシンタックスを使った定義 ○ @wrap, @register, @initialize, @expose を基本のデコレーターとする ■ 新たなデコレーターを定義するには基本のデコレーターを組み合わせる ● 静的に変換(トランスパイル)できるようにして実行速度を速くする class Klass { @wrap(func) method() { } } class Klass { method() { } } Klass.prototype.method = func(Klass.prototype.method);

Slide 17

Slide 17 text

17 2019 Static Decorators (問題点) ● まだまだ最適化が困難 ○ V8 チームの分析によりこのまま進めるのは難しいという結論に至った ■ トランスパイルも大事だが、実装エンジンの制約を考えると厳しい ● 表現力 vs 記述の容易さ vs 最適化のトレードオフ 👉 そもそもデコレーターには何が求められているのか調査しよう!

Slide 18

Slide 18 text

18 https://docs.google.com/spreadsheets/d/1QP0hfXkkkAXTktGrI7qrt-RUqKp2KtsVKuPo4yuoZZI

Slide 19

Slide 19 text

19 2020 Read/Write Trapping Decorators (ユーザーからの提案 tc39/proposal-decorators#299) ● 函数による定義 ○ フィールド/メソッドデコレーターでプロパティを getter, setter でラップする ■ プロパティディスクリプタを加工できないため表現力は落ちる ■ Proxy のトラップと似た仕様なため実行速度は遅くない(はず) function logged() { return { set(target, instance, name, original) { return function wrapper(...args) { console.log(`called ${name}`); return original.call(this, ...args); }; }, }; }

Slide 20

Slide 20 text

20 2021 Current Decorators ● 函数による定義 ○ デコレーターの種類のよってできることを制限する ■ ユースケースをカバーしつつ、表現力をある程度犠牲に type ClassDecorator = (value: Function, context: Context) => Function | void; type FieldDecorator = (value: undefined, context: Context) => ((initialValue: unknown) => unknown) | void; type MethodDecorator = (value: Function, context: Context) => Function | void; interface Context { kind: "class" | "field" | "method"; name?: string | symbol; isPrivate?: boolean; ... }

Slide 21

Slide 21 text

21 まとめ ● デコレーターの提案には表現力、記述の容易さ、最適化のトレードオフがある ○ 議論に6年以上かかっている ○ まだ Stage 2 で仕様が確定的ではないので、もうしばらく議論が続きそう ● 現在ライブラリなど広く使われているデコレーターは 2015 年のもので非推奨 ● あなたも TC39 会議の議論を追ってみませんか? ○ 入社すると ECMAScript 提案の最新情報が勝手に流れてきます!流します!