Proposal: Add Map.prototype.putIfAbsent

Sathya Gunasekaran gsathya at chromium.org
Mon Oct 15 12:48:58 UTC 2018


On Thu, Oct 11, 2018 at 6:29 AM Andrea Giammarchi
<andrea.giammarchi at gmail.com> wrote:
>
> No. The raised concern has been common among developers and the main issue is that `set` returns itself which is the least useful pattern.
>
> Your suggestion would make sense if `const value = map.has(key) ? map.get(key) : map.set(key, createValue())` instead ES went for chain ability and yet, to date, I have to see a single usage of `map.set(a, 1).set(b, 2)` in the wild.
>
> On top of that, needing to `.has` and then `.get` is a performance shenanigan many would likely avoid at all costs 'cause AFAIK in no engine `.has(key)` temporarily retains last searched key to boost up the immediate `.get(key)` later on so having a `.putIfAbsent(key, createValue())` that returns either the found or the created value is a clear win.
>

V8 implemented this[0] and then reverted it[1] because it didn't
actually help with performance. IIRC, JSC still has this optimization.

[0]: https://chromium.googlesource.com/v8/v8/+/3ca6408511900328e11175c38b128b3a6cebb7ec
[1]: https://chromium.googlesource.com/v8/v8/+/a27daf04a36ead5484e7a09a447af918555006dc

>
>
> On Thu, Oct 11, 2018 at 6:20 AM Jordan Harband <ljharb at gmail.com> wrote:
>>
>> Thanks, your correction explains what the benefit would be (the awkward need to cache the value). However this seems simpler to me: `if (!map.has(key)) { map.set(key, getValue()); } const value = map.get(key);`
>>
>> On Wed, Oct 10, 2018 at 9:07 PM Isiah Meadows <isiahmeadows at gmail.com> wrote:
>>>
>>> I presume you mean this?
>>>
>>> ```js
>>> // Proposed
>>> map.putIfAbsent(key, init)
>>>
>>> // How you do it now
>>> let value
>>> if (map.has(key)) {
>>>     value = map.get(key)
>>> } else {
>>>     map.set(key, value = init())
>>> }
>>> ```
>>>
>>> BTW, I'd like to see this make it myself, just slightly different:
>>>
>>> - How you call it: `map.getOrPut(key, init, thisValue=undefined)`
>>> - How `init` is called: `init.call(thisValue, key, map)`
>>>
>>> This pattern is incredibly common for caching. It'd be nice if I didn't have to repeat myself so much with it. I would be more willing to stuff it in a utility function if it weren't for the fact the use cases often entail performance-sensitive paths, and engines aren't reliable enough in my experience with inlining closures passed to non-builtins.
>>>
>>> On Wed, Oct 10, 2018 at 22:54 Jordan Harband <ljharb at gmail.com> wrote:
>>>>
>>>> It seems like your proposed `const value = map.putIfAbsent(key, getExpensiveValue);` is achievable already with `const value = map.has(key) ? map.get(key) : map.set(getExpensiveValue());` - am I understanding your suggestion correctly?
>>>>
>>>> On Wed, Oct 10, 2018 at 12:46 AM Man Hoang <jolleekin at outlook.com> wrote:
>>>>>
>>>>> Consider the following function
>>>>>
>>>>> ``` js
>>>>>
>>>>> /**
>>>>>
>>>>> * Parses the locale sensitive string [value] into a number.
>>>>>
>>>>> */
>>>>>
>>>>> export function parseNumber(
>>>>>
>>>>>     value: string,
>>>>>
>>>>>     locale: string = navigator.language
>>>>>
>>>>> ): number {
>>>>>
>>>>>     let decimalSeparator = decimalSeparators.get(locale);
>>>>>
>>>>>     if (!decimalSeparator) {
>>>>>
>>>>>         decimalSeparator = Intl.NumberFormat(locale).format(1.1)[1];
>>>>>
>>>>>         decimalSeparators.set(locale, decimalSeparator);
>>>>>
>>>>>     }
>>>>>
>>>>>
>>>>>
>>>>>     let cleanRegExp = regExps.get(decimalSeparator);
>>>>>
>>>>>     if (!cleanRegExp) {
>>>>>
>>>>>         cleanRegExp = new RegExp(`[^-+0-9${decimalSeparator}]`, 'g');
>>>>>
>>>>>         regExps.set(decimalSeparator, cleanRegExp);
>>>>>
>>>>>     }
>>>>>
>>>>>
>>>>>
>>>>>     value = value
>>>>>
>>>>>         .replace(cleanRegExp, '')
>>>>>
>>>>>         .replace(decimalSeparator, '.');
>>>>>
>>>>>
>>>>>
>>>>>     return parseFloat(value);
>>>>>
>>>>> }
>>>>>
>>>>>
>>>>>
>>>>> const decimalSeparators = new Map<string, string>();
>>>>>
>>>>> const regExps = new Map<string, RegExp>();
>>>>>
>>>>> ```
>>>>>
>>>>>
>>>>>
>>>>> This function can be simplified quite a bit as follows
>>>>>
>>>>> ``` js
>>>>>
>>>>> export function parseNumber(
>>>>>
>>>>>     value: string,
>>>>>
>>>>>     locale: string = navigator.language
>>>>>
>>>>> ): number {
>>>>>
>>>>>     const decimalSeparator = decimalSeparators.putIfAbsent(
>>>>>
>>>>>         locale, () => Intl.NumberFormat(locale).format(1.1)[1]);
>>>>>
>>>>>
>>>>>
>>>>>     const cleanRegExp = regExps.putIfAbsent(
>>>>>
>>>>>         decimalSeparator, () => new RegExp(`[^-+0-9${decimalSeparator}]`, 'g'));
>>>>>
>>>>>
>>>>>
>>>>>     value = value
>>>>>
>>>>>         .replace(cleanRegExp, '')
>>>>>
>>>>>         .replace(decimalSeparator, '.');
>>>>>
>>>>>
>>>>>
>>>>>     return parseFloat(value);
>>>>>
>>>>> }
>>>>>
>>>>> ```
>>>>>
>>>>> if `Map` has the following instance method
>>>>>
>>>>> ``` js
>>>>>
>>>>> export class Map<K, V> {
>>>>>
>>>>>     /**
>>>>>
>>>>>      * Look up the value of [key], or add a new value if it isn't there.
>>>>>
>>>>>      *
>>>>>
>>>>>      * Returns the value associated to [key], if there is one.
>>>>>
>>>>>      * Otherwise calls [ifAbsent] to get a new value, associates [key] to
>>>>>
>>>>>      * that value, and then returns the new value.
>>>>>
>>>>>      */
>>>>>
>>>>>     putIfAbsent(key: K, ifAbsent: () => V): V {
>>>>>
>>>>>         let v = this.get(key);
>>>>>
>>>>>         if (v === undefined) {
>>>>>
>>>>>             v = ifAbsent();
>>>>>
>>>>>             this.set(key, v);
>>>>>
>>>>>         }
>>>>>
>>>>>         return v;
>>>>>
>>>>>     }
>>>>>
>>>>> }
>>>>>
>>>>> ```
>>>>>
>>>>>
>>>>>
>>>>> Java's Map has a `putIfAbsent` method, which accepts a value rather than a function for the second parameter. This is not ideal as computing the value may be expensive. A function would allow the value to be computed lazily.
>>>>>
>>>>>
>>>>>
>>>>> References
>>>>>
>>>>> - [Dart] [Map.putIfAbsent](https://api.dartlang.org/stable/2.0.0/dart-core/Map/putIfAbsent.html)
>>>>>
>>>>> - [Java] [Map.putIfAbsent](https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#putIfAbsent-K-V-)
>>>>>
>>>>> _______________________________________________
>>>>> es-discuss mailing list
>>>>> es-discuss at mozilla.org
>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>
>>>> _______________________________________________
>>>> es-discuss mailing list
>>>> es-discuss at mozilla.org
>>>> https://mail.mozilla.org/listinfo/es-discuss
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss


More information about the es-discuss mailing list