Proposal: Add Map.prototype.putIfAbsent

Andrea Giammarchi andrea.giammarchi at gmail.com
Thu Oct 11 04:29:39 UTC 2018


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.



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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20181011/d98a46a0/attachment.html>


More information about the es-discuss mailing list