A different semantics for WeakMap#get default value

# David Bruant (13 years ago)

I recently wrote some code using WeakMaps and the following pattern keeps coming over and over:

var v = wm.get(o); if(v === undefined){ v = someValue; order.set(o, v); }

// now, wn has a value for o (either it already did or it's been added) // v === wm.get(o) // play with v

The "v === undefined" is fine in my case, because I know I never store "undefined" as a value.

I thought that maybe the semantics of WeakMap#get could be changed whenever the key is not used to set the value and return it. My pattern would be reduced to:

var v = wm.get(o, someValue); // now, wn has a value for o (either it already did or it's been added) // v === wm.get(o) // play with v

Thinking more about the current semantics, I thought that what is currently done could be achieved a bit differently:

var v = wm.get(o, def); // almost equivalent to: var v = wm.get(o) || def;

There is a difference when the vaue stored in the weak map is a falsy value.

My personal experience is to store mostly objects (so truthy) as weak map values, so I wouldn't be affected. Has anyone else experience in storing falsy values?

# Tab Atkins Jr. (13 years ago)

On Mon, Jan 16, 2012 at 12:09 PM, David Bruant <bruant.d at gmail.com> wrote:

Thinking more about the current semantics, I thought that what is currently done could be achieved a bit differently:

var v = wm.get(o, def); // almost equivalent to: var v = wm.get(o) || def;

This is a very common semantic in dictionaries, and it's extremely useful. I recommend adding it, either to get() as you describe, or in a second function. Python implements it both ways - the get() function can take a second "default" value that is returned if the key is not in the dict, and it provides a setdefault() function that sets the key if and only if it's not already set, and then returns the key's value.

# Axel Rauschmayer (13 years ago)

One probably should separate two concerns: providing a default value vs. adding missing values to the collection.

One possible signature:

 function get(key, default = undefined, addIfMissing = false)

The above would also allow you to store undefined and detect missing values, by using a special value (e.g. named NO_VALUE) as the default.

# Mark S. Miller (13 years ago)

I like the idea of the two argument get as proposed by David. (In fact, I like it so much that I thought I'd already specified it that way ;).) David, I'm glad to see you got it right in your suggestion at < harmony:weak_maps#alternate_spec_based_on_ff6.0a1

.

Axel, I do not like your elaboration because it turns a query operation into a possibly occasionally side-effecting operation. As it is now, I can give someone a read-only view of a WeakMap wm my simply giving them

{ get: wm.get.bind(wm), has: wm.has.bind(wm) }
# Mark S. Miller (13 years ago)

On Mon, Jan 16, 2012 at 12:56 PM, Mark S. Miller <erights at google.com> wrote:

I like the idea of the two argument get as proposed by David. (In fact, I like it so much that I thought I'd already specified it that way ;).) David, I'm glad to see you got it right in your suggestion at < harmony:weak_maps#alternate_spec_based_on_ff6.0a1

.

Axel, I do not like your elaboration because it turns a query operation into a possibly occasionally side-effecting operation. As it is now, I can give someone a read-only view of a WeakMap wm my simply giving them

... by simply giving them...

# Andrea Giammarchi (13 years ago)

I would also say that if you can carry both the keyObject and its defaultValue with you all the time you may not need at all to store it as weak map ... looks to me you are missing the point where the relation should be created, using the get() as generic action/entry point to do that.

I believe relate a keyObject with undefined does not simply make sense in any daily basis logic I can think of so a function or a method as extension of the WeakMap.prototype would solve everything with similar signature suggested by Axel, isn't it?

br

# David Bruant (13 years ago)

[Specifying the Davids]

Le 16/01/2012 21:56, Mark S. Miller a écrit :

I like the idea of the two argument get as proposed by David [BRUANT]. (In fact, I like it so much that I thought I'd already specified it that way ;).) David [HERMAN], I'm glad to see you got it right in your suggestion at harmony:weak_maps#alternate_spec_based_on_ff6.0a1.

Actually, that's not what I meant. What I meant was to replace the get that is currently where you link to with

get(key, defaultValue = undefined) { if (key !== Object(key)) { throw new TypeError(...); // key must be an object } const i = private(this).keys.indexOf(key);

  if(i < 0 && defaultValue !== undefined){
    this.set(key, defaultValue);
  }

  return private(this).values[i];

}

In this version, if the key is not found, the property is set to the return value. The return statement returns the value that is there if the key existed in the map or the default value (which has just been assigned).

# Axel Rauschmayer (13 years ago)

A separate method such as the previously mentioned setdefault() provided by Python [1] might answer both Mark’s concerns and yours.

[1] docs.python.org/dev/library/stdtypes.html#mapping

# Mark S. Miller (13 years ago)

David and Axel, I don't get it. What's wrong with the get with the optional default value, as specified by that text and as currently implemented by FF? What additional value does this contingent mutation add, regardless of how it's packaged? Do you really want "has" to answer true for a key merely because a previous get (or setdefault) with that key caused a default value to be stored? Axel, does this add enough value to expand the API by 25%?

# Andrea Giammarchi (13 years ago)

I think I agree with current wm.get(o, def) which is indeed the one implemented in Firefox and all other shims I have seen so far ... I don't agree about the third argument to set it automatically if not there: a bit too much for a get semantic, imho

# Brendan Eich (13 years ago)

Clearly(!) a set-if-not-present method should not be misnamed "get".

I like the optional sentinel-meaning-not-found for get, and setDefault per Python as Tab pointed out. Agree they should not be merged into one API. Bikeshedding setDefault at leisure (in background in my head ;-).

# Brendan Eich (13 years ago)

Mark objected to the mutating-option-for-"get" overloading. I've agreed but wanted to go further here: optional trailing boolean parameters are an anti-pattern. I'm leaning toward thinking that all boolean option params are an anti-pattern.

# David Bruant (13 years ago)

Le 16/01/2012 21:56, Mark S. Miller a écrit :

I like the idea of the two argument get as proposed by David. (In fact, I like it so much that I thought I'd already specified it that way ;).) David, I'm glad to see you got it right in your suggestion at harmony:weak_maps#alternate_spec_based_on_ff6.0a1.

Axel, I do not like your elaboration because it turns a query operation into a possibly occasionally side-effecting operation. As it is now, I can give someone a read-only view of a WeakMap wm my simply giving them

{ get: wm.get.bind(wm), has: wm.has.bind(wm) }

This applies to my proposal as well. Bind can allow currying the arguments. Unfortunately, currying here would happen in the wrong order (currying the key, not the default value).

wm.get.bindReverseCurry(2, wm, undefined) would be what you want (sorry for the bad name)

f.bindReverseCurry(end, thisVal, ...args) returns g such as: g(...gargs) <=>

f.apply(thisVal, gargs.slice(0, end - argsToRevCurry.length).concat(args) ) (I'll deal with end < argsToRevCurry.length another day :-) )


var slice = Function.prototype.call.bind(Array.prototype.slice);

Function.prototype.bindReverseCurry = function(end, thisVal){ var toCall = this; var argsToRevCurry = slice(arguments, 1);

return function(){
    var args = slice(arguments, 0, end - argsToRevCurry.length);
    return toCall.apply(thisVal, args.concat(argsToRevCurry));
};

}

// test: parseInt2 = parseInt.bindReverseCurry(2, undefined, 2); console.log(parseInt2("101")); // 5

Would such a solution sounds satisfactory to provide a read-only view of a weakmap?

# David Bruant (13 years ago)

Le 16/01/2012 22:46, Mark S. Miller a écrit :

David and Axel, I don't get it. What's wrong with the get with the optional default value, as specified by that text and as currently implemented by FF?

This discussion started with me saying that "var v = wm.get(o, someValue);" could be a shortcut for:

var v = wm.get(o); if(v === undefined){ v = someValue; order.set(o, v); }

which is a pattern I'm writing over and over. There is nothing inherently wrong with the current design, I just wanted to see if the semantics of the default value could be added.

What additional value does this contingent mutation add, regardless of how it's packaged?

What additional value adds the default value when something very close is achieved with "wm.get(o) || defaultValue"? Do you have use cases of storing falsy values in WeakMaps? If so, then the difference matters.

Do you really want "has" to answer true for a key merely because a previous get (or setdefault) with that key caused a default value to be stored?

If the get does a "set" only if the default value is different than 'undefined', I think it's worth it.

Taking a step back, what is the use case for:

wm.set(o, undefined); wm.has(o); // true

It seems that it's more the use case of a WeakSet than a WeakMap, no?

# Mark S. Miller (13 years ago)

On Mon, Jan 16, 2012 at 2:11 PM, David Bruant <bruant.d at gmail.com> wrote: [...]

What additional value does this contingent mutation add, regardless of how it's packaged? What additional value adds the default value when something very close is achieved with "wm.get(o) || defaultValue"? Do you have use cases of storing falsy values in WeakMaps? If so, then the difference matters.

That's precisely what I'm confused about. I agree that "||" doesn't work when the map is used to store falsy values. But the FF get/2 works perfectly fine:

!!0;

false

!!'';

false

var wm = WeakMap(); var x = {}; var y = {}; wm.set(x, 0) wm.get(x, '')

0

wm.get(y, '') === '';

true

wm.has(x)

true

wm.has(y)

false

That seems ideal.

# Axel Rauschmayer (13 years ago)

I’m not sure how much this is needed for WeakMaps in particular, but when you nest data structures then it’s handy to automatically create an entry if it is missing. I’ve seen collection frameworks where maps have an optional constructor argument: a function that auto-creates an entry if it is missing.

But we are entering complex API territory and those decisions might best be delegated to whoever creates a collections library for JavaScript.

# Axel Rauschmayer (13 years ago)

Mark objected to the mutating-option-for-"get" overloading. I've agreed but wanted to go further here: optional trailing boolean parameters are an anti-pattern. I'm leaning toward thinking that all boolean option params are an anti-pattern.

All boolean parameters are anti-patterns? All optional boolean parameters? What’s the alternative?

I thought that boolean parameters where OK when they were labeled in some manner (e.g. via an object literal). But I always find unlabeled parameters problematic (beyond the first one). Some of it can be mitigated by choosing good function/method names. Furthermore, trailing callable parameters are often self-explanatory.

# Brendan Eich (13 years ago)

Axel Rauschmayer <mailto:axel at rauschma.de> January 16, 2012 2:33 PM

All boolean parameters are anti-patterns? All optional boolean parameters? What’s the alternative?

I'm talking about formal parameters here.

I thought that boolean parameters where OK when they were labeled in some manner (e.g. via an object literal).

Those are not parameters, they are properties of an object parameter (which may conventionally be expressed literallly in the actual argument).

But I always find unlabeled parameters problematic (beyond the first one). Some of it can be mitigated by choosing good function/method names. Furthermore, trailing callable parameters are often self-explanatory.

String-valued parameters are self-documenting when used well. Same goes for enum in C/C++. Just vague true and false parameters, not so much.

# Andrea Giammarchi (13 years ago)

then the method should be called "getOrSetIfNotThere(obj, def)" 'cause get() would mislead too much

looks to me we are also confusing falsy with undefined ... undefined is undefined and there's no reason to use a WeakMap to set undefined since I would expect the WeakMap itself to completely ignore the operation of "setting" (or relating the keyObject)

Of course if undefined it will always be undefined ... what the second argument could do instead is to relate keyObject with a defined value

var o = wm.get(obj) || def;

is not the same of

var o = wm.get(obj, def);

'cause in first case "get(obj)" could be any falsy value while in the second one def will be returned if obj was in the WeakMap and it could be falsy too but no || operator is used so you are sure that the value of "o" is the one you expected but never undefined ( unless def is undefined itself but then you don't need at all to pass it as second argument )

A "too magic get()" would mean no set is needed anymore so either you call the method differently, or something is wrong with the logic used with WeakMaps.

Said that, has() could be optimal for all those cases where you want to * set()* the *keyObject *if not there yet. This could be optimized in core too as few engines do already for similar operations and the resulting JS code would be:

if (!wm.has(obj)) { wm.set(obj, def); } var o = wm.get(obj);

with has() the need for second get() argument would be kinda pointless except for all lazy devs out there (ahem ...!)

br

# Andrea Giammarchi (13 years ago)

On Tue, Jan 17, 2012 at 1:39 AM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:

the second one def will be returned if obj was in the WeakMap

sorry for the typo, I meant if obj was not in the WeakMap ... pretty sure you got it

br

# Kris Kowal (13 years ago)

On Mon, Jan 16, 2012 at 4:39 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

then the method should be called "getOrSetIfNotThere(obj, def)" 'cause get() would mislead too much

I’ve been calling this method "getset" for about three years, hoping it would catch on :P

kriskowal/narwhal-lib/blob/f182f2f0a952d75f06b0ebe142696056d2933501/lib/narwhal/util.js

Kris Kowal

# Andrea Giammarchi (13 years ago)

it's OK, 'cause is not called get ;-)

# Kris Kowal (13 years ago)

For what it’s worth, Python dictionaries have .get(key, default) and .setdefault(key, default). The former is non-mutating. Its behavior is also different depending on arguments length. If the default is not passed, it will throw an exception. If a default is passed, even if that default is None, it will not throw an exception. I’ve found both forms useful.

Kris Kowal

# David Bruant (13 years ago)

Le 17/01/2012 01:46, Kris Kowal a écrit :

On Mon, Jan 16, 2012 at 4:39 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

then the method should be called "getOrSetIfNotThere(obj, def)" 'cause get() would mislead too much I’ve been calling this method "getset" for about three years, hoping it would catch on :P

kriskowal/narwhal-lib/blob/f182f2f0a952d75f06b0ebe142696056d2933501/lib/narwhal/util.js

Indeed, I thought afterwards, that I could just as well create my own function and my own abstraction. It can be built efficiently on top of the current API, so everything's good.

# David Bruant (13 years ago)

Le 17/01/2012 01:53, Kris Kowal a écrit :

For what it’s worth, Python dictionaries have .get(key, default) and .setdefault(key, default). The former is non-mutating. Its behavior is also different depending on arguments length. If the default is not passed, it will throw an exception. If a default is passed, even if that default is None, it will not throw an exception. I’ve found both forms useful.

A use case I have in my case is that the "default value" may be different. For instance, generating a new number. I think that generators could do the trick here, but when using a generator as the "default" argument of .setDefault, how can the engine tell if i want it to use the generator as value or as value generator?

# Herby Vojčík (13 years ago)

Brendan Eich wrote:

Clearly(!) a set-if-not-present method should not be misnamed "get".

I like the optional sentinel-meaning-not-found for get, and setDefault per Python as Tab pointed out. Agree they should not be merged into one API. Bikeshedding setDefault at leisure (in background in my head ;-).

Like: getIfAbsentSet(key, dflt) with dflt being mandatory? Still there is question of laziness of dflt, the original #get:ifAbsentPut: has the block as its second argument.

# François REMY (13 years ago)

If I just can add my own two cents about the naming convention :

get(key)
get(key, valueIfMissing)

set(key, newValue)

getOrSet(key, newValueIfMissing)
// kinda like openOrCreate on most file systems

François

-----Message d'origine---

# Allen Wirfs-Brock (13 years ago)

On Jan 17, 2012, at 3:45 AM, Herby Vojčík wrote:

Brendan Eich wrote:

Clearly(!) a set-if-not-present method should not be misnamed "get".

I like the optional sentinel-meaning-not-found for get, and setDefault per Python as Tab pointed out. Agree they should not be merged into one API. Bikeshedding setDefault at leisure (in background in my head ;-).

Like: getIfAbsentSet(key, dflt) with dflt being mandatory? Still there is question of laziness of dflt, the original #get:ifAbsentPut: has the block as its second argument.

The Smalltalk solution to this problem is the at: key ifAbsent: block method.

This subsumes both get with default value and get create if missing use cases plus others. In JS we might say:

col.getIfAbsent(key) {|| 42}; //use 42 if no present

col.getIfAbsent(key) {|| col.defaultValue}; //have collection provide the default value

col.getIfAbsent(key) {|| col.put(key, 42); 42} //if key isn't there add it with an appropriate vaue

# Herby Vojčík (13 years ago)

Allen Wirfs-Brock wrote:

On Jan 17, 2012, at 3:45 AM, Herby Vojčík wrote:

Brendan Eich wrote:

Clearly(!) a set-if-not-present method should not be misnamed "get".

I like the optional sentinel-meaning-not-found for get, and setDefault per Python as Tab pointed out. Agree they should not be merged into one API. Bikeshedding setDefault at leisure (in background in my head ;-). Like: getIfAbsentSet(key, dflt) with dflt being mandatory? Still there is question of laziness of dflt, the original #get:ifAbsentPut: has the block as its second argument.

The Smalltalk solution to this problem is the at: key ifAbsent: block method.

There is also at: key ifAbsentPut: block At least in Squeak it was there.

This subsumes both get with default value and get create if missing use cases plus others. In JS we might say:

col.getIfAbsent(key) {|| 42}; //use 42 if no present

col.getIfAbsent(key) {|| col.defaultValue}; //have collection provide the default value

col.getIfAbsent(key) {|| col.put(key, 42); 42} //if key isn't there add it with an appropriate vaue

except it computes collection twice. If it is something like:

foo.bar().getIfAbsent(key) {|| foo.bar().put(key, 42); 42}

it is a bit clumsy. You can pass the collection itself in an argument

foo.bar().getIfAbsent(key) {|c| c.put(key, 42); 42}

but you have the same problem with value, if it is computed, you must

foo.bar().getIfAbsent(key) {|c| let v = baz(); c.put(key, v); v}

so it seems better to have different api and write

foo.bar().getIfAbsentSet(key) {|| baz()}

Also, Mark S. Miller wrote:

As it is now, I can give someone a read-only view of a WeakMap wm my simply giving them

{ get: wm.get.bind(wm), has: wm.has.bind(wm) }

so distinct API is for writeback version is probably not a bad idea.

# Jason Orendorff (13 years ago)

On Mon, Jan 16, 2012 at 2:09 PM, David Bruant <bruant.d at gmail.com> wrote:

Hi,

I recently wrote some code using WeakMaps and the following pattern keeps coming over and over:

var v = wm.get(o); if(v === undefined){    v = someValue;    wm.set(o, v); }

Yes, this pattern is common. In 2000, Guido van Rossum added the dict.setdefault method to Python in order to address this need.

Six years later, he had come to consider it a failure. mail.python.org/pipermail/python-dev/2006-February/061169.html

I think the setdefault method failed for three reasons:

  1. Its second argument was a value, not a factory function.

  2. It just wasn't intuitive. Even to the people most familiar with it, it was always a conscious effort to read or write code using setdefault.

  3. It required programmers to repeat themselves. The default value had to be specified every place the map was queried rather than just once at the point where the data structure was created.

The result of the discussion linked above was the introduction of collections.defaultdict to the Python standard library: docs.python.org/library/collections.html#defaultdict-objects

To me, defaultdict was a relief, and long overdue. It solved all three problems with setdefault. I've been using it happily ever since. Ruby has a similar feature built into its core Hash type.

So, what about ECMAScript? I think problems 2 and 3 apply to the various getset/getOrSet/getIfAbsentSet ideas proposed in this thread. Here is my proposal:

WeakMap.prototype.force = function (key) {
    if (this.has(key))
        return this.get(key);
    var v = this.default(key);
    this.set(key, v);
    return v;
};

If a particular Map or WeakMap is intended to be used with force(), the program must supply it with a .default() method, which it could do just by assignment, maps being extensible objects:

var table = new WeakMap;
table.default = function () { return new Array; };

(The name "force" is intended to evoke mutation and lazy evaluation. Maybe that's too obscure. It could be called "require" instead, which reads like a slightly stronger form of "get".)

# Herby Vojčík (13 years ago)

Jason Orendorff wrote:

... In 2000, Guido van Rossum added the dict.setdefault method to Python in order to address this need.

Six years later, he had come to consider it a failure. mail.python.org/pipermail/python-dev/2006-February/061169.html

I think the setdefault method failed for three reasons:

  1. Its second argument was a value, not a factory function.

  2. It just wasn't intuitive. Even to the people most familiar with it, it was always a conscious effort to read or write code using setdefault.

  3. It required programmers to repeat themselves. The default value had to be specified every place the map was queried rather than just once at the point where the data structure was created.

...

So, what about ECMAScript? I think problems 2 and 3 apply to the various getset/getOrSet/getIfAbsentSet ideas proposed in this thread.

Probably not wholly constructive remark, but for the original #at:ifAbsentPut: from Smalltalk (which I ported as getIfAbsentSet) is very readable. Maybe not as readable is ES since keyword does not separate parameters, but I'd say 2 does not suffer as much.

As for 3., it's true. If you can compute the "default" from the key alone, you proposal below is fine. The question what is the percentage of cases where the ifAbsentPut: argument value is in fact bound to the calling context (for which explicit per-call block to compute the value is better than per-collection[ class]).

Here is my proposal:

 WeakMap.prototype.force = function (key) {
     if (this.has(key))
         return this.get(key);
     var v = this.default(key);
     this.set(key, v);
     return v;
 };

If a particular Map or WeakMap is intended to be used with force(), the program must supply it with a .default() method, which it could do just by assignment, maps being extensible objects:

 var table = new WeakMap;
 table.default = function () { return new Array; };

But nice idea, too. If it is fit for 80% of situations, it is probably better. I still feel a little uneasy about the fact that I must set up the default for the collection. When to set it? I've got a feeling the ghost of malloc/free is materializing behind this (that is, problems with who should set the default method and when). But maybe it is not the problem; it is used in Python, after all.

# Mark S. Miller (13 years ago)

On Wed, Jan 18, 2012 at 11:07 AM, Jason Orendorff <jason.orendorff at gmail.com

wrote: [...]

The result of the discussion linked above was the introduction of collections.defaultdict to the Python standard library: docs.python.org/library/collections.html#defaultdict-objects

To me, defaultdict was a relief, and long overdue. It solved all three problems with setdefault. I've been using it happily ever since. Ruby has a similar feature built into its core Hash type.

Hi Jason, I like the idea that the Python defaultdict seems almost to be, which I'll call an InfiniteMap. For JS, the following InfiniteMap actually obeys the Map contract while implementing a Map with an infinite number of key-value mappings. It's initial state is total -- it provides a mapping for every key, and so, in this initial state, infMap.has(key) would always return true. It does this by using the lazyFactory function as needed to make the infinite population of values initially associated with each of the infinite population of keys.

Because an instance of InfiniteMap conforms to the full Map contract (given that baseMap does and is not otherwise used), we have full Liskov substitutability -- you can validly pass an InfiniteMap to an abstraction expecting a Map. If baseMap and lazyFactory are well behaved, including having no externally observable side effects, then although the get() method below internally causes a side effect, get() does not observably cause any side effects. For all observations possible by a client of a well behaved InfiniteMap instance, get() remains a pure query operation.

Everyone on this thread, is there any need expressed in this thread that is not satisfied by InfiniteMap?

function InfiniteMap(baseMap, lazyFactory) { "use strict";

var pumpkin = Object.freeze(Object.create(null)); // should not escape var tombstone = Object.freeze(Object.create(null)); // should not escape

return { get: function(key, defaulValue /= void 0/) { var result = baseMap.get(key, pumpkin); if (result === pumpkin) { result = lazyFactory(); baseMap.set(key, result); return result; } if (result === tombstone) { return defaultValue; } return result; }, has: function(key) { return baseMap.get(key, pumpkin) !== tombstone; }, set: baseMap.set.bind(baseMap),

'delete': function(key) {
  baseMap.set(key, tombstone);
}

}; }

# Axel Rauschmayer (13 years ago)

On Jan 19, 2012, at 7:51 , Mark S. Miller wrote:

Everyone on this thread, is there any need expressed in this thread that is not satisfied by InfiniteMap?

Very good solution: It solves a use case that is not that common via an extra type (and uses delegation, to boot). Hence, a nice separation of concerns. This would solve all of the use cases that I have had so far.

==> Typo: defaulValue

# Andreas Rossberg (13 years ago)

On 19 January 2012 07:51, Mark S. Miller <erights at google.com> wrote:

Everyone on this thread, is there any need expressed in this thread that is not satisfied by InfiniteMap?

Looks good, except that I don't understand why you want a lazyFactory function instead of a simple initialValue -- if you recommend lazyFactory having no observable side effect, then (with no arguments) it will be a constant function, and you could as well use a plain value.

On the other hand, a function would be useful if it also got the key as an argument, so that it can manufacture values in a key-dependent manner.

# Andrea Giammarchi (13 years ago)

On Thu, Jan 19, 2012 at 7:51 AM, Mark S. Miller <erights at google.com> wrote:

Everyone on this thread, is there any need expressed in this thread that is not satisfied by InfiniteMap?

I would say for "notification purpose"

result = lazyFactory(key, defaultValue);

would be more appropriate otherwise the defaultValue looses completely its meaning the moment "key" is not there due the first if.

Out of curiosity, Chrome experimental flag does not support get(key, defaultValue) but get(key) only ... is this something missing or Map and WeakMap will never support officially the second get() argument?

br

# Andreas Rossberg (13 years ago)

On 19 January 2012 07:51, Mark S. Miller <erights at google.com> wrote:

Because an instance of InfiniteMap conforms to the full Map contract (given that baseMap does and is not otherwise used), we have full Liskov substitutability -- you can validly pass an InfiniteMap to an abstraction expecting a Map.

PS: Won't the "full Map contract" eventually contain iterators, too? An infinite map cannot sensibly support those.

# Andreas Rossberg (13 years ago)

On 19 January 2012 09:00, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

Out of curiosity, Chrome experimental flag does not support get(key, defaultValue) but get(key) only ... is this something missing or Map and WeakMap will never support officially the second get() argument?

Chrome/V8 simply implements what the current proposals say:

harmony:simple_maps_and_sets, harmony:weak_maps

Of course, we are happy to adapt if the proposals get extended along the lines you mention.

# Mark S. Miller (13 years ago)

On Wed, Jan 18, 2012 at 11:37 PM, Andreas Rossberg <rossberg at google.com>wrote:

On 19 January 2012 07:51, Mark S. Miller <erights at google.com> wrote:

Everyone on this thread, is there any need expressed in this thread that is not satisfied by InfiniteMap?

Looks good, except that I don't understand why you want a lazyFactory function instead of a simple initialValue -- if you recommend lazyFactory having no observable side effect, then (with no arguments) it will be a constant function, and you could as well use a plain value.

On the other hand, a function would be useful if it also got the key as an argument, so that it can manufacture values in a key-dependent manner.

I think providing the key, or the key and defaultValue as Andrea suggests is a fine idea. However, even without these, the answer to your question is "fresh mutable state" as in

function lazyFactory() { return Set(); }

or similarly function lazyFactory() { return []; } etc

This addresses the only concrete use cases I've seen mentioned in this thread -- using nested single-key single-valued collections to emulate either

a) multi-valued collections, i.e., MultiMaps, as single-valued collections from keys to sets of values, or

b) multi-keyed collections, i.e., a two dimensional map, as a map from a first key to a collection mapping from a second key (array index) to a value.

# Mark S. Miller (13 years ago)

On Thu, Jan 19, 2012 at 12:00 AM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:

On Thu, Jan 19, 2012 at 7:51 AM, Mark S. Miller <erights at google.com>wrote:

Everyone on this thread, is there any need expressed in this thread that is not satisfied by InfiniteMap?

I would say for "notification purpose"

result = lazyFactory(key, defaultValue);

would be more appropriate

Hi Andrea, as I just mentioned in my reply to Andreas, I think this is a good suggestion. However,

otherwise the defaultValue looses completely its meaning the moment "key" is not there due the first if.

That's not quite true in the collection I posted, since an InfiniteMap is only initially total. It still emulates deletes by using tombstones to poke holes into its initially universal domain. When doing a get at a deleted key, the defaultValue would come into effect.

# Andrea Giammarchi (13 years ago)

On Thu, Jan 19, 2012 at 6:11 PM, Mark S. Miller <erights at google.com> wrote:

That's not quite true in the collection I posted, since an InfiniteMap is only initially total. It still emulates deletes by using tombstones to poke holes into its initially universal domain. When doing a get at a deleted key, the defaultValue would come into effect.

got it, my concern was mainly about the very first case where defaultValue is not even considered and we have two completely different behaviors if we get before or after a key has been set ... no way to use the lazyFactory(key, defaultValue) { ... do stuff and return defaultValue }, if necessary, in order to be able to instantly set that default ( I just find weird this "need to delete first, then get default after" )

Hope you got what I mean

br

# Mark S. Miller (13 years ago)

On Thu, Jan 19, 2012 at 12:44 AM, Andreas Rossberg <rossberg at google.com>wrote:

On 19 January 2012 07:51, Mark S. Miller <erights at google.com> wrote:

Because an instance of InfiniteMap conforms to the full Map contract (given that baseMap does and is not otherwise used), we have full Liskov substitutability -- you can validly pass an InfiniteMap to an abstraction expecting a Map.

PS: Won't the "full Map contract" eventually contain iterators, too? An infinite map cannot sensibly support those.

Depends what you mean by "sensibly". I see nothing at harmony:iterators that implies that iterators must or even should terminate. And infinite iterators (in general though not here) are clearly useful.

However, I agree that doing an infinite iteration over an InfiniteMap is unlikely to be useful. For example, the contract can almost be trivially and uselessly satisfied by having the InfiniteMap return the following "items" (key-value pair) iterator:

{ next: function() { return [{}, lazyFactory()]; } }

Since each of these keys are fresh, we know we don't need to consult the baseMap for an overriding mapping. Since there are an infinite number of such keys, we know they'll never run out, so we can indefinitely postpone ever needing to return a meaningful mapping. However the "almost" qualifier above is because, to be correct, the iterator would have to store the new useless associations in the baseMap before returning them -- making this technique even more useless.

Infinities are indeed weird.

# Mark S. Miller (13 years ago)

I will try to write a complete proposal and settle it at the March meeting. I'll propose today that this be on the March agenda. I'll keep it extremely small.

# Jason Orendorff (13 years ago)

On Thu, Jan 19, 2012 at 12:51 AM, Mark S. Miller <erights at google.com> wrote:

Hi Jason, I like the idea that the Python defaultdict seems almost to be, which I'll call an InfiniteMap. For JS, the following InfiniteMap actually obeys the Map contract while implementing a Map with an infinite number of key-value mappings. It's initial state is total -- it provides a mapping for every key, and so, in this initial state, infMap.has(key) would always return true. It does this by using the lazyFactory function as needed to make the infinite population of values initially associated with each of the infinite population of keys.

That was my first impression too, during the 2006 discussion when defaultdict was being designed. The issue was raised. But Guido intentionally made defaultdict inconsistent with the Mapping contract, on the grounds that being able to access the actual data was much more important than the invariant. He was right. I think InfiniteMap would be unsuitable for most defaultdict use cases. Consider:

# Python
def mostFrequentWord(words):
    counts = defaultdict(lambda: 0)
    for word in words:
        counts[word] += 1
    return max(counts.keys(), key=lambda w: counts[w])

// ES with InfiniteMap
function mostFrequentWord(words) {
    var counts = InfiniteMap(function () { return 0; });
    for (var word of words)
        counts.set(word, counts.get(word) + 1);
    //...now what?
}

Defaulting is useful when building a data structure. But data structures also need to be queried, aggregated, and updated, and at that point the program usually needs to see the data as it is—an impenetrable illusion of infinity is counterproductive.

Anyway, note that David's motivating use cases are not cases where he needs to pass the Map to Map-consuming library code and needs an object that satisfies Map's contract. Rather, Map is being used as a concrete type (as usual for core types) and its API is just clumsy for this particular purpose. That need can be addressed directly.