monolithic kernel

ES6 の Map のコレジャナイ感

YAPC::Asia 2015 Tokyo にて、teppeis さんによる ECMAScript 6 (ES6) についての講演を聞いてきました。ES6 を特に追い掛けていなかった私にとっては、ざっくりとした概略を掴むことができる有意義な内容でした。これまではただただ貧弱という印象だった JavaScript が、ついにそれなりにまともに書けるようになりそうだということで、ES6 の普及が楽しみになりました。

さて、発表の中では新機能の紹介がいくつかあったのですが、そんな中でも、列挙されていただけで紹介はされなかった Map に興味を持ちました。ここで言う Map というのは、Ruby で言うところの Hash、C++ でいうところの *_map のような連想配列のことです。

そもそも、JavaScript の Object は連想配列として使えるので、なんで今さら Map を追加するのか、と思う方もいらっしゃるかもしれません。確かにその通りで、大抵の場合は Object で足りるというのも事実です。ただ、Object のキーには String しか使えないという制限があるのです。そのため、たまにある String 以外をキーにしたいという場合には、なんとか文字列表現に落とし込むか (というか勝手に toString されますが)、Array に格納して O(n) の検索コストを支払うか、頑張って String 以外をキーにできる Map を自分で実装するかのどれかを選ぶ必要がありました。

この String 以外を連想配列に使いたいという若干ニッチな、でも実際にコードを書いていると割と出てくる要望を叶えてくれるのが、ES6 で追加される Map です。これがあれば、文字列表現になることによる扱いにくさを気にする必要も、余計な検索コストを支払う必要も、車輪の再発明を繰り返す必要もありません。

しかもこの機能、ES6 と言っても未来の話ではなく、大体のブラウザの最近のバージョンで利用可能なようです。new Map() したら普通にインスタンスを作れたのでちょっと驚きました。

まずは普通に文字列をキーにしてみます。

var map = new Map();
map.set('a', 123);
console.log(map.get('a')); // => 123

いい感じですね。次は配列を試してみます。

var map = new Map();
map.set([ 1, 2, 3 ], 123);
console.log(map.get([ 1, 2, 3 ])); // => undefined

つらい。

Mozilla のドキュメントを読んでみたところ、キーの同一性は “same-value” アルゴリズムに基いていて、=== 演算子と同じセマンティクスとのこと。[][] が “same-value” じゃなかったら一体何なんだろうと思いながらも[] === [] を試してみると、確かに false になります。どうやら、オブジェクトに対して === 演算子を使うと、同じオブジェクトを参照しているときに等しいとされるようです。

というわけで、以下のようにするとうまくいきます。

var map = new Map();
var key = [ 1, 2, 3 ];
map.set(key, 123);
console.log(map.get(key)); // => 123

個人的には、複数のデータを配列に突っ込んでの group by (Ruby だと array.group_by {|item| [ item.foo, item.bar ] } みたいな感じ) や、JSON をパースしてできるようなデータのオブジェクトをキーにしてデータを格納するといった用途を想定していたので、そのどちらにも使えそうにないというのは残念でなりません。というか、逆にこの Map はどんなユースケースを想定しているんでしょうか……。