How to “computeIfAbsent” / “getOrElseUpdate” a Map in JavaScript?

Assuming that

  • m is a Map<number, V> for some type V
  • k is a number,

how do I write down an expression that

  • either gets an already existing V for the key k, or
  • creates a new v: V, puts it into the map for the key k, and evaluates to v?

For example, SOME_EXPR(m, k, []) should either return m.get(k) if it already exists, or put the [] into m.set(k, []) and return the [].


Concrete example

Suppose that I want to incrementally build a Map<number, number[]>.
I want to assign values 100 and 200 to the key 48, and value 300 to 52.
I want to create new empty arrays on demand whenever needed.
For this, I need something like an SOME_EXPR(map, key, value) such that

var m = new Map(); // Map<number, number[]>
SOME_EXPR(m, 48, []).push(100)
SOME_EXPR(m, 48, []).push(200)
SOME_EXPR(m, 52, []).push(300)

results in a map

{ 48 -> [100, 200]; 52 -> [300] }

What I’ve tried

I could, of course, create a helper method:

function getOrElseUpdate(m, k, defaultValue) {
  if (!m.has(k)) {
    m.set(k, defaultValue);
  }
  return m.get(k);
}

and then use SOME_EXPR(m, k, []) := getOrElseUpdate(m, k, []). But not only does it have to compute the hash code thrice, it’s also just heavyweight and annoying (and possibly not obvious to the maintainer of the code, who has to click on it to see the definition in yet another file etc.).

I could try to inline this somehow:

SOME_EXPR(m,k,v) := ((k) => (m.get(k) || ((v) => (m.set(k, v), v))(v)))(k)

so that the above example would become

var m = new Map();
((k) => (m.get(k) || ((v) => (m.set(k, v), v))([])))(42).push(100);
((k) => (m.get(k) || ((v) => (m.set(k, v), v))([])))(42).push(200);
((k) => (m.get(k) || ((v) => (m.set(k, v), v))([])))(58).push(300);

which works, but is just bizarre.

I’ve also tried looking around for related answers, but this search turned out frustratingly unfruitful.

Is there any idiomatic way to achieve that?


Analogous methods from some other languages

(optional; skip this if you’re not into JVM)

In Scala, it would look like somewhat like this:

val m = HashMap.empty[Int, ListBuffer[Int]]
m.getOrElseUpdate(48, ListBuffer.empty) += 100
m.getOrElseUpdate(48, ListBuffer.empty) += 200
m.getOrElseUpdate(52, ListBuffer.empty) += 300

// m is now:
//
// HashMap(
//   48 -> ListBuffer(100, 200), 
//   52 -> ListBuffer(300)
// )

In Java, very similarly:

HashMap<Integer, List<Integer>> m = new HashMap<>();
m.computeIfAbsent(42, k -> new LinkedList<>()).add(100);
m.computeIfAbsent(42, k -> new LinkedList<>()).add(200);
m.computeIfAbsent(58, k -> new LinkedList<>()).add(300);

// m = {58=[300], 42=[100, 200]}