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

Stage 2 Decorators の変遷 / Stage 2 Decorators his...

Stage 2 Decorators の変遷 / Stage 2 Decorators history

PIXIV DEV MEETUP EVENT エンジニア勉強会
https://conference.pixiv.co.jp/2021/dev-meetup

petamoriken / 森建

May 07, 2021
Tweet

More Decks by petamoriken / 森建

Other Decks in Programming

Transcript

  1. 2 自己紹介 • moriken (petamoriken) • 2017年5月入社(アルバイト) • プロダクト支援本部課題解決部(福岡) •

    JavaScript が好きで TC39 会議を追ってます ◦ 「moriken scrapbox」で検索 ◦ 2018 年あたりから大体の ESNext の議論の 流れが日本語で読めます!(最近サボり気味) petamoriken エンジニア
  2. 3 Ecma International TC39 • JavaScript の言語仕様 ECMAScript を策定する専門委員会 •

    提案一覧は GitHub 上で管理されている ◦ https://github.com/tc39/proposals • だいたい2ヶ月ごとに会議を開いている ◦ アジェンダ: https://github.com/tc39/agendas ◦ 議事録: https://github.com/tc39/notes
  3. 4 ECMAScript の策定プロセス • Stage 1 Proposal ◦ TC39 の担当者が決まる

    • Stage 2 Draft ◦ 仕様草案が作られる • Stage 3 Candidate ◦ 仕様がほぼ確定し、レビューが完了する • Stage 4 Finished (ES2022) ◦ polyfill ではない2つの実装で問題がないことが確認される
  4. 5 Decorators とは • 現状 Stage 2 Draft にある提案仕様(草案) •

    他の言語ではアノテーションと呼ばれたりする • JavaScript のクラスを拡張するシンタックスの追加 ◦ クラスに修飾する位置でおおまかに 3つに分類できる 👉 • トランスパイルして既に広く使われている ◦ NestJS や Angular など @classDecorator class Klass { @fieldDecorator prop = 1; @methodDecorator method() { } }
  5. // 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
  6. // 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
  7. 11 2015 First Decorators • 函数による定義 ◦ 引数にクラス自身、プロパティのキー、プロパティディスクリプタ ◦ プロパティディスクリプタを加工して返す

    function nonenumerable(target, key, descriptor) { descriptor.enumerable = false; return descriptor; } class Person { @nonenumerable get kidCount() { return this.children.length; } }
  8. 12 2015 First Decorators (問題点) • Private Fields に対するフィールドデコレーターをどうするか function

    fieldDecorator(target, key, descriptor) { console.log(key); // ??? } class Klass { @fieldDecorator #x = 0; } 👉 Class Fields の提案にあわせてデコレーターの仕様も新しくしよう!
  9. 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
  10. 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 ... }
  11. 15 2018 Descriptor-based Decorators (問題点) • 仕様が複雑 • 処理が重い/実行速度が遅い ◦

    いままでは Object.defineProperty 相当の機能だったのに対して、 今回のこの仕様ではなんでも出来すぎる ◦ 最適化が困難 👉 なんでもできる表現力そのままにもっと速く実行できるようにしよう!
  12. 16 2019 Static Decorators • 新しいシンタックスを使った定義 ◦ @wrap, @register, @initialize,

    @expose を基本のデコレーターとする ▪ 新たなデコレーターを定義するには基本のデコレーターを組み合わせる • 静的に変換(トランスパイル)できるようにして実行速度を速くする class Klass { @wrap(func) method() { } } class Klass { method() { } } Klass.prototype.method = func(Klass.prototype.method);
  13. 17 2019 Static Decorators (問題点) • まだまだ最適化が困難 ◦ V8 チームの分析によりこのまま進めるのは難しいという結論に至った

    ▪ トランスパイルも大事だが、実装エンジンの制約を考えると厳しい • 表現力 vs 記述の容易さ vs 最適化のトレードオフ 👉 そもそもデコレーターには何が求められているのか調査しよう!
  14. 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); }; }, }; }
  15. 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; ... }
  16. 21 まとめ • デコレーターの提案には表現力、記述の容易さ、最適化のトレードオフがある ◦ 議論に6年以上かかっている ◦ まだ Stage 2

    で仕様が確定的ではないので、もうしばらく議論が続きそう • 現在ライブラリなど広く使われているデコレーターは 2015 年のもので非推奨 • あなたも TC39 会議の議論を追ってみませんか? ◦ 入社すると ECMAScript 提案の最新情報が勝手に流れてきます!流します!