Proposal: Add Map.prototype.putIfAbsent

Isiah Meadows isiahmeadows at gmail.com
Thu Oct 11 04:47:20 UTC 2018


It's slghtly simpler in terms of lines of code, but it's still more awkward
than it should be for that kind of thing.

On Thu, Oct 11, 2018 at 00:20 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
>>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20181011/e0d3929f/attachment-0001.html>


More information about the es-discuss mailing list