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

Stage 2 Decorators の変遷 / Stage 2 Decorators history

森建
May 07, 2021

Stage 2 Decorators の変遷 / Stage 2 Decorators history

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

森建

May 07, 2021
Tweet

More Decks by 森建

Other Decks in Programming

Transcript

  1. Stage 2 Decorators の変遷
    pixiv Inc.
    petamoriken
    2021.5.7

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. 6 https://nestjs.com/

    View Slide

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

    View Slide

  8. // 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

    View Slide

  9. // 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  13. 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

    View Slide

  14. 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
    ...
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. 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);
    };
    },
    };
    }

    View Slide

  20. 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;
    ...
    }

    View Slide

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

    View Slide