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

**2023 年 11 月 28 日追記：**[第 99 回 TC39 ミーティング](https://github.com/tc39/agendas/blob/main/2023/11.md "会議の議事日程")が行われたことに伴い記事を更新しました。また、サンプルコードにある致命的なミスを発覚し修正しました。

---

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

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

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

# SmooshGate 事件

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

メソッド自体を解説する文章ならいくらでもありますのでそれを省きさせていただきます。念のため [MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) と[仕様書](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.flat)へのリンクです。同様に [TC39](https://tc39.es) の紹介も省きます。

## `Array.prototype.flatten`

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

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

https://github.com/tc39/proposal-flatMap

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

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

[MooTools](https://mootools.net) というビルトインオブジェクトの prototype をいわゆる monkey-patch するライブラリを使っているサイトがこのプロポーザルによって[壊される](https://bugzilla.mozilla.org/show_bug.cgi?id=1443630 "Mozilla のバグ報告ページ")とのこと。昔はかなり有名なライブラリで、多くのサイトに使われているため、無視することができません。

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

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

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

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

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

ここで注意したいところは、`for-in` ループは[列挙可能](https://developer.mozilla.org/ja/docs/Web/JavaScript/Enumerability_and_ownership_of_properties "MDN により説明")（[enumerable](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#table-object-property-attributes "仕様書の Table 3: Attributes of an Object property")）なプロパティしか反復しないことです。仕様書の[第 18 章の部分](https://tc39.es/ecma262/multipage/ecmascript-standard-built-in-objects.html#:~:text=Every%20other%20data%20property%20described%20in%20clauses%2019%20through%2028%20and%20in%20Annex%20B.2%20has%20the%20attributes%20%7B%20%5B%5BWritable%5D%5D%3A%20true%2C%20%5B%5BEnumerable%5D%5D%3A%20false%2C%20%5B%5BConfigurable%5D%5D%3A%20true%20%7D%20unless%20otherwise%20specified.)によりネイティブメソッドはすべて列挙不可能になっており、古いバージョンにはない `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](https://www.spacejam.com/1996/) など、古いサイトでも閲覧できるままであり続けるよう、破壊的変更（breaking change）を極力せず Web 全体を守るという HTML、CSS、ECMAScript などの Web に関わるすべての標準にも適用される原則があります。それが「Don’t break the Web」です。

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

## 改名へ

話は戻ります。みんなが名前を何にしようと議論を始めた際、[あるプルリクエスト](https://github.com/tc39/proposal-flatMap/pull/56 "プロポーザルリポジトリの Pull Request #56")が出されました。この冗談半分に出したメソッド名を `flatten` から `smoosh` に変えるプルリクは広汎な議論を呼び、​‑gate という「不祥事」を意味する英語の接尾辞にちなんで誰かが事件を「SmooshGate」とまでハッシュタグ付きで名付けました。

そして 2018 年 5 月、TC39 の[第 64 回会議](https://github.com/tc39/agendas/blob/main/2018/05.md "会議の議事日程")にて `flat` に改名することが決まり[^2]、事件は一件落着しました。

[^2]: 他にも候補として `flattened` や `squash` などが挙げられました。

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

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

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

https://github.com/tc39/proposal-array-grouping

Stage 3 に達した時点で、提案は `Array.prototype.groupBy` と `Array.prototype.groupByToMap`[^3] の形を取っていました：

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

```js
["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](https://sugarjs.com) という別の monkey-patching ライブラリの特定のバージョンを使う多くのサイトが[動かなくなります](https://github.com/tc39/proposal-array-grouping/issues/37 "プロポーザルリポジトリの Issue #37")。

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

そして、同年 6 月の[第 90 回会議](https://github.com/tc39/agendas/blob/main/2022/06.md "会議の議事日程")にて `group` への改名が決まりました。それからの 11 月——

## Web 互換性問題、再び

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

また、発覚されたこれらの[互換性問題](https://github.com/tc39/proposal-array-grouping/issues/44 "プロポーザルリポジトリの Issue #44")により Google Chrome はバージョン 108 で `Array.prototype.{group,groupToMap}` を実装することを諦めました。

そして現在に至り、新しい名前として `groupToObject` や `grouping` や `grouped` や `groupedBy` などの候補はありますが、<ruby>轍<rt>てつ</ruby>を何度も踏まないよう、新たな選択肢は作られました。

2022 年 11 月の[第 93 回 TC39 ミーティング](https://github.com/tc39/agendas/blob/main/2022/11.md "会議の議事日程")にて静的メソッド `Object.groupBy` と `Map.groupBy` にする提案が[プルリク](https://github.com/tc39/proposal-array-grouping/pull/47 "プロポーザルリポジトリの Pull Request #44")とともに出されました。これは将来的に `WeakMap.groupBy` や [Records & Tuples プロポーザル](https://github.com/tc39/proposal-record-tuple "プロポーザルの GitHub リポジトリ")における `Record.groupBy` に拡張することを踏まえると、こちらの方がやや好まれるそうです。

```js
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 に達し](https://github.com/tc39/ecma262/pull/3176 "仕様書のリポジトリの Pull Request #3176")、仕様の一部になりました。

# おわりに

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

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

# 謝辞

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

- [Don’t break the Web：以 SmooshGate 以及 keygen 為例](https://blog.techbridge.cc/2019/11/30/smooshgate-and-keygen/)<sub>（繁体字中国語）</sub>（前半）
- [SmooshGate FAQ](https://developers.google.com/web/updates/2018/03/smooshgate)<sub>（英語）</sub>（前半）
- [Stage 3 Array Grouing プロポーザル の Web の互換性の問題](https://sosukesuzuki.dev/advent/2022/01/) by @sosukesuzuki（後半）