LoginSignup
123
40

メソッドの名付けは決して甘くない——二つの JavaScript プロポーザルが歩んだ道のり

Last updated at Posted at 2023-03-23

2023 年 11 月 28 日追記:第 99 回 TC39 ミーティングが行われたことに伴い記事を更新しました。また、サンプルコードにある致命的なミスを発覚し修正しました。


はじめて技術記事を書いてみました。ぐらふぃーむと申します。

ECMAScript(いわゆる JavaScript)の先端を操る TC39 に関する情報が(少なくとも日本語コミュニティでは)思うより少なかったため初回は「SmooshGate 事件」と「Array Grouping プロポーザル」を取り上げようと思います。

拙い文章なのでおかしいところがあればご指摘願います。編集リクエスト機能もご活用ください。

SmooshGate 事件

2023 年になって Array.prototype.flat メソッドを知らない JavaScript デベロッパーはほとんどいないでしょう。しかしその裏にはあまり知られていない、メソッドの名前や運命に関わる出来事があります。それが「SmooshGate 事件」です。

メソッド自体を解説する文章ならいくらでもありますのでそれを省きさせていただきます。念のため MDN仕様書へのリンクです。同様に TC39 の紹介も省きます。

Array.prototype.flatten

これが元々のメソッド名です。「flatten」の方が動詞ですし Lodash の方にちなんだ名前を取って 2017 年 7 月1Array.prototype.{flatten,flatMap} というプロポーザルが立てられました。それからはずっと順調で、半年も掛からず Stage 3 に達しました。

しかし、翌年となる 2018 年 3 月、問題が発覚されました。

これがブラウザで実装されるとサイトが壊れる!

MooTools というビルトインオブジェクトの prototype をいわゆる monkey-patch するライブラリを使っているサイトがこのプロポーザルによって壊されるとのこと。昔はかなり有名なライブラリで、多くのサイトに使われているため、無視することができません。

なぜ問題は MooTools によって引き起こしたのか

説明はやや厄介になるので飛ばして構いません。

MooTools では仕様に従わない独自の flatten メソッドが定義され、Array.prototype に直接代入します。よって Array.prototype.flatten がブラウザで実装されるとしても、MooTools のコードは後で実行するので当然ネイティブの実装を上書きします。特に何もないように見えますが——

あいにく、MooTools のやることはこれだけではありません。MooTools には Elements という独自の API があり、Array.prototype に代入された独自メソッドは以下のように Elements.prototype にコピーされます:

for (var key in Array.prototype) {
  Elements.prototype[key] = Array.prototype[key];
}

ここで注意したいところは、for-in ループは列挙可能enumerable)なプロパティしか反復しないことです。仕様書の第 18 章の部分によりネイティブメソッドはすべて列挙不可能になっており、古いバージョンにはない Object.defineProperty などを使わない限り値が書き換えられてもプロパティの属性は変えることがありません。よって Array.prototype.flatten がネイティブに実装されたらメソッドは Elements.prototype にコピーされませんので、Elements.prototype.flatten に依存するコードがあるすべてのサイトが壊れることになります。

そこで「Array.prototype.flatten を特別に列挙可能にすればいいじゃん」と思うあなたは大間違いです。ES5 以前は for-of さえもないので、Array.prototype.forEach を使わざるを得ません。しかし関数型プログラミングを嫌がっている人やパフォーマンス面に配慮のある人はみんな for-in ループを使って配列の各要素を反復していました。もしも Array.prototype.flatten を列挙可能にすると、"flatten" プロパティは "0""1""2"……などのように for-in ループに含まれることになるので、さらに多くのコードが壊されるでしょう。

どうせ古いサイトばっかだし、そのまま破ったら?

海外では有名な Space Jam など、古いサイトでも閲覧できるままであり続けるよう、破壊的変更(breaking change)を極力せず Web 全体を守るという HTML、CSS、ECMAScript などの Web に関わるすべての標準にも適用される原則があります。それが「Don’t break the Web」です。

仮に何かの新しい機能が搭載されることによってサイトが動かなくなったら、突如閲覧できなくなる訪問者だけでなく、何もしてないのに直さなければならないサイトの所有者もデベロッパーも困ります。さらに「別のブラウザでは動くじゃん」と気づいたユーザーはそのまま使っているブラウザから去るため、市場シェアを失ったブラウザベンダーは困り、他のブラウザは実装を拒否するので、標準化担当の方々も困ります。すべての関係者にも不利益をもたらす最悪の結末になりかねます。

改名へ

話は戻ります。みんなが名前を何にしようと議論を始めた際、あるプルリクエストが出されました。この冗談半分に出したメソッド名を flatten から smoosh に変えるプルリクは広汎な議論を呼び、​‑gate という「不祥事」を意味する英語の接尾辞にちなんで誰かが事件を「SmooshGate」とまでハッシュタグ付きで名付けました。

そして 2018 年 5 月、TC39 の第 64 回会議にて flat に改名することが決まり2、事件は一件落着しました。

2019 年 1 月、Array.prototype.{flat,flatMap} プロポーザルは Stage 4 に達し、正式に仕様書に載せることを果たしました。

一難去ってまた一難——Array Grouping プロポーザル

一難ところか二難です。Array Grouping という 2021 年 7 月に立てられたプロポーザルもまた前述のプロポーザルと同じくらいな速さで、同年 12 月に Stage 3 に達しました。

Stage 3 に達した時点で、提案は Array.prototype.groupByArray.prototype.groupByToMap3 の形を取っていました:

["hoge", "fuga", "foo", "piyo", "bar", "baz"].groupBy(({ length }) => length);
// { 4: ["hoge", "fuga", "piyo"], 3: ["foo", "bar", "baz"] }
["hoge", "fuga", "foo", "piyo", "bar", "baz"].groupByToMap(({ length }) => length);
// Map { 4: ["hoge", "fuga", "piyo"], 3: ["foo", "bar", "baz"] }

しかし、翌月となる 2022 年 1 月、問題が発覚されました。

サイトがまた壊れる!

今度はブラウザが Array.prototype.groupBy を搭載すると Sugar.js という別の monkey-patching ライブラリの特定のバージョンを使う多くのサイトが動かなくなります

Sugar.js はネイティブ実装の有無によってメソッドを置き換えるかどうかを決めます。Sugar.js の実装は "hoge.fuga[0..3].piyo[-1]" のような文字列による再帰的プロパティアクセスや第2引数などの独自機能があり、Array.prototype.groupBy をブラウザ側が実装すると古いバージョンの Sugar.js は上書きしないため、これらの独自機能が使えなくなります。これによって、メソッド名の変更を余儀なくされました。

そして、同年 6 月の第 90 回会議にて group への改名が決まりました。それからの 11 月——

Web 互換性問題、再び

group という単語はごく一般的なため、User[] & { group?: "admin" | "member" | undefined } のような形で、普通のプロパティ名として使われがちです。この例では、ユーザーグループは設定済みかどうかを真偽チェックするなどが考えられます。実際にこれに似た原因で URL が ……/login/realms?group=function group() { [native code] } になってしまうケースも見られます。

また、発覚されたこれらの互換性問題により Google Chrome はバージョン 108 で Array.prototype.{group,groupToMap} を実装することを諦めました。

そして現在に至り、新しい名前として groupToObjectgroupinggroupedgroupedBy などの候補はありますが、てつを何度も踏まないよう、新たな選択肢は作られました。

2022 年 11 月の第 93 回 TC39 ミーティングにて静的メソッド Object.groupByMap.groupBy にする提案がプルリクとともに出されました。これは将来的に WeakMap.groupByRecords & Tuples プロポーザルにおける Record.groupBy に拡張することを踏まえると、こちらの方がやや好まれるそうです。

Object.groupBy(["hoge", "fuga", "foo", "piyo", "bar", "baz"], ({ length }) => length);
// { 4: ["hoge", "fuga", "piyo"], 3: ["foo", "bar", "baz"] }
Map.groupBy(["hoge", "fuga", "foo", "piyo", "bar", "baz"], ({ length }) => length);
// Map { 4: ["hoge", "fuga", "piyo"], 3: ["foo", "bar", "baz"] }

翌年 11 月、提案はこの形で Stage 4 に達し、仕様の一部になりました。

おわりに

Array.prototype.{flat,flatMap} と Array Grouping という二つの ECMAScript プロポーザルの歴史を紹介しました。TC39 の方々は Web を壊さないよう多大な努力をしていることが分かります。

この初めての記事はすごく時間が掛かりました。それでも技術的な話だけでなく文章を書くコツについても沢山学びましたので、それはそれで値すると思います。皆さんのご意見をコメント欄でお待ちしております。

謝辞

こちらの記事を参考にさせていただいてはじめて、この文章を書き上げることができました。ありがとうございます。

  1. これは Stage 0 になる月で、リポジトリが作成したのは 2016 年 2 月です。

  2. 他にも候補として flattenedsquash などが挙げられました。

  3. Array.prototype.groupByMap は Stage 2 に達した同時に追加され、Stage 3 に進むことに合わせて groupByToMap に改名しました。

123
40
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
123
40