LLMS

MapのgetOrInsert、getOrInsertComputedで冗長なコードを減らす

Baseline 2026で追加されたMap.prototype.getOrInsertとgetOrInsertComputedを紹介します。キーが存在しない場合にデフォルト値を挿入して返す新しいメソッドで、グループ化やカウントなどの定番パターンを簡潔に書けます。

公開: 2026年3月2日(月)
更新: 2026年3月2日(月)

はじめに

Mapを使う際に、以下のようなコードを書いたことはないでしょうか。

const map = new Map();
if (!map.has(key)) {
  map.set(key, INIT_VALUE);
}
return map.get(key) ?? INIT_VALUE;

Mapにキーが存在しない場合は新たに初期値をセットして、存在する場合はその値を返す、というパターンです。

グループ化やカウントなど頻出のパターンですがhassetgetの3ステップが毎回必要で冗長です。 TypeScriptではhasで存在を確認した後でもgetの返り値がV | undefinedのままで型が絞り込まれないため、非nullアサーション(!)や??での対処も必要になります。

Baseline 2026で追加されたgetOrInsertgetOrInsertComputedを使うと、このパターンをすっきり書けるようになります。 TypeScriptでもv6.0で型定義が追加される予定です(Announcing TypeScript 6.0 Beta)。

getOrInsertgetOrInsertComputedは、Mapの他にWeakMapにも同様のメソッドが追加されますが、ここでは説明の便宜上Mapを例に紹介します。

getOrInsert

const map = new Map<K, V>();
map.getOrInsert(key: K, defaultValue: V): V

getOrInsertは2つの引数を取ります。

  • key:取得または挿入するキー
  • defaultValue:キーが存在しない場合に挿入・返却する値

キーが存在すれば既存の値をそのまま返し、存在しなければdefaultValuesetしてから返します。

const map1 = new Map();
console.log(map1.getOrInsert('a', 0)); //  0(挿入して返す)
console.log(map1.getOrInsert('a', 1)); //  0(既存値を返す)

const map2 = new Map();
console.log(map2.getOrInsert('a', 1)); //  1(挿入して返す)
console.log(map2.getOrInsert('a', 0)); //  1(既存値を返す)

冒頭に紹介したコードは以下のように簡潔に記述できます。

const map = new Map<string, typeof INIT_VALUE>();
return map.getOrInsert(key, INIT_VALUE);

ORMではfindOrCreateといった名前で同等の操作がよく提供されています。getOrInsertはそれと同じ考え方をMapにもたらすものです。

getOrInsertComputed

実はgetOrInsertには注意点があります。第2引数はメソッドを呼び出した時点で評価されるため、キーが既に存在する場合でもデフォルト値の式が実行されてしまいます。

//'a'が存在していてもexpensiveCompute()は必ず実行される
map.getOrInsert('a', expensiveCompute());

デフォルト値の生成コストが高い場合にはこれが問題になります。getOrInsertComputedはこの問題を解決するために用意されたメソッドです。

const map = new Map<K, V>();
map.getOrInsertComputed(key: K, callbackFn: (key: K) => V): V

getOrInsertComputedの第2引数には値ではなく関数を渡します。

  • key: 取得または挿入するキー
  • callbackFn: キーが存在しない場合のみ呼ばれる関数。keyを引数として受け取り、挿入する値を返す

キーが存在すればcallbackFnは呼ばれません。存在しない場合のみcallbackFn(key)を実行し、その返り値をsetして返します。

const map = new Map<string, number>();

const callback = (key: string) => {
  // コールバックが実際に呼ばれたかどうかをログで確認する(本来は副作用を避けるべき)
  console.log(`"${key}" のコールバックを実行`);
  return key.length;
};

map.getOrInsertComputed('a', callback);
// ログ: "a" のコールバックを実行(キーが存在しないため呼ばれる)

map.getOrInsertComputed('a', callback);
// ログなし(キーが既に存在するため呼ばれない)

コールバックの引数にはキーが渡されるため、キーから値を導出するパターンにも適しています。

// Intl.NumberFormat のインスタンスはコストが高いのでキャッシュしておく
const formatters = new Map();

const createFormatter = (locale) => new Intl.NumberFormat(locale);

formatters.getOrInsertComputed('ja-JP', createFormatter);
formatters.getOrInsertComputed('en-US', createFormatter);

TypeScript 6.0での対応

執筆当時のTypeScriptの最新バージョン5.9では、getOrInsertgetOrInsertComputedの型が存在しないことに注意してください。

今の所、TypeScript 6.0esnextライブラリに型定義が追加される予定です。

interface Map<K, V> {
    getOrInsert(key: K, defaultValue: V): V;
    getOrInsertComputed(key: K, callback: (key: K) => V): V;
}

interface WeakMap<K extends WeakKey, V> {
    getOrInsert(key: K, defaultValue: V): V;
    getOrInsertComputed(key: K, callback: (key: K) => V): V;
}

getの返り値V | undefinedに対して、getOrInsertgetOrInsertComputedは常にVを返すことが型レベルで保証されるようになり非常に嬉しいです。

おわりに

getOrInsertgetOrInsertComputedは、Mapで頻出する「なければ初期値を入れてから取得する」パターンを1行で書けるようにするメソッドです。

getOrInsertは値を直接渡す即時評価、getOrInsertComputedはコールバックによる遅延評価という使い分けを押さえておけば、場面に合わせて適切に選択できます。

0
もくじ

©︎ 2024〜2026 k8o. All Rights Reserved.