{Map, WeakMap, etc}.prototype.getRef(key)

When working with Javascript objects, we have access to a large variety of operators, such as obj.x++, obj.x ??= 2, or obj.x **= 2 to get the job done. It's unfortunately not possible to use operators such as those when accessing values from an instances of Map, WeakMap, etc. This leads to highly unreadable code to do basic operations:

// Increment a value
map.set(map.get('myKey') + 1)

// Set a non-existent value
if (!map.has('myKey')) {
  map.set('defaultValue')
}

This has caused us to start looking into creating awkward convenience functions to do what's already possible to do with these operators, like the proposed emplace() function here which attempts to solve the past two scenarios as follows:

// Increment a value
counts.emplace('myKey', {
  update: existing => existing + 1
});

// Set a non-existent value
map.emplace('myKey', {
  insert: () => 'defaultValue'
});

I propose we add a .getRef() method to Map, Set, and any other data structure that could benefit from it. It will return a reference with a "value" getter/setter property that can be used to update that specific entry. It can be defined somewhat like this:

// Note that browsers can add a bit of caching,
// so a lookup doesn't have to happen with each ref operation.
Map.prototype.getRef = function(key) {
  const self = this
  return {
    get value() {
      return self.get(key)
    },
    set value(newVal) {
      self.set(key, newVal)
    },
    exists() {
      return self.has(key)
    },
  }
}

Now we can write our previous example in a very simple, straight-forward way:

// Increment a value
map.getRef('myKey').value++

// Set a non-existent value
map.getRef('myKey').value ??= 'defaultValue'

Some other motivating examples:

// Insert 0 if doesn't exist, then increment
const ref = map.getRef(key)
(ref.value ??= 0)++

// Another insert or update example
const ref = map.getRef(computeKey())
ref.value = !ref.exists() ? createNew : updateValue(ref.value)

I'd much rather have a kind of Map that works like Python's defaultdict.

const defmap = new DefaultMap(() => ({value:0}));
defmap.get(key).value++;
// equiv to:
const map = new Map();
(map.has(key) || map.set(key, {value:0}), map.get(key)).value++;
1 Like

It does not get the reference of the key, but create a new reference every time you call it.
i.e.

map.getRef('myKey') != map.getRef('myKey') 

Good?

@simonkcleung

Yes, it would return a new object every time you request the reference, so map.getRef('myKey') != map.getRef('myKey') would be true.

@lightmare - I do think default-dicts would solve many of the problems that .getRef() solves, and maybe if that kind of feature goes in, the need for .getRef() would be lessened. But, your particular solution of having a default dict automatically create objects that you then mutate works, but it feels a little "tricky" - plus it forces you to structure the contents of the map in a specific format - you can't just use primitives as values. Still, it's an interesting idea, and it shows that default dicts can be used to achieve the same objectives.