Private symbols as WeakMap sugar

# David Bruant (12 years ago)

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

 var wm = new WeakMap();
 var o = {};
 o[wm] = 12 // desugars to wm.set(o, 12)
 var a = o[wm]; // desugars to wm.get(o);
 wm in o // desugars to wm.has(o);
 delete o[wm] // desugars to wm.delete(o);

Benefits

Private symbols are out

No need for the specific freeze behavior. No need for the unknownPrivateSymbol trap. Unique symbols do remain though

MarkM's hint

The use of a different syntax for a different usage would be the hint. It's possible that both syntax will be used, but in all likelihood, only one syntax will be used per weakmap, clearly indicating to the engine which storage and implementation characteristics should be preferred. Worst case, the performance of unclear code is a bit worse.

Inheritance

Both "o[wm]" and "wm in o" could desugar to a slightly more useful algorithm involving proto-climbing thus emulating inheritance as we know it.

 var w = new WeakMap();
 var o = {};
 o[w] = 37;
 var o2 = Object.create(o);
 console.log(o2[w]); // w.has(o2) returns false, w.has(o) return 

true. w.get(o); logs 37.

Downsides

Object.defineProperty & friends don't work with weakmap as property name

No getter/setter, no configurability, no hasOwnProperty. I see that as a benefit actually. YMMV It makes weakmaps-as-private-symbol inconsistent with unique symbol. I'm also ok with this.

Confusion?

Maybe people will be confused by this bi-use of the same feature. Maybe the sugar will result in everyone using it and not caring of WeakMap.prototype methods, defeating the "hint" point above.

Open question

What happens when the WeakMap is wrapped?

 var w = new WeakMap();
 var ww = new Proxy(w, {});
 var o = {};
 o[ww] = 12; // ww.set(o) calling the ww's set trap?
 // should a/4 new trap(s) be created for internal weakmap methods?
# Brendan Eich (12 years ago)

David Bruant wrote:

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap();
var o = {};
o[wm] = 12 // desugars to wm.set(o, 12)
var a = o[wm]; // desugars to wm.get(o);
wm in o // desugars to wm.has(o);
delete o[wm] // desugars to wm.delete(o); 

You are not showing the desugaring in general:

function get(o,x) { return o[x]; }

must transform to

function get(o,x) { return (x is WeakMap) ? x.get(o) : o[x]; }

This is not an acceptable hit for every []-named property access.

# David Bruant (12 years ago)

Le 16/01/2013 19:42, Brendan Eich a écrit :

David Bruant wrote:

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap();
var o = {};
o[wm] = 12 // desugars to wm.set(o, 12)
var a = o[wm]; // desugars to wm.get(o);
wm in o // desugars to wm.has(o);
delete o[wm] // desugars to wm.delete(o); 

You are not showing the desugaring in general:

function get(o,x) { return o[x]; }

must transform to

function get(o,x) { return (x is WeakMap) ? x.get(o) : o[x]; }

You're right, I hadn't thought of that.

This is not an acceptable hit for every []-named property access.

I intuit (and may be wrong) that even just observing types (weakmap or stringified type) passed to a []-access can be a good indicator of how best []-named property access should be JIT-compiled. You pay the price of the (x is WeakMap) test only the time the JIT warms up. Given what's in current engines, it sounds doable.

# Allen Wirfs-Brock (12 years ago)

On Jan 16, 2013, at 10:53 AM, David Bruant wrote:

Le 16/01/2013 19:42, Brendan Eich a écrit :

David Bruant wrote:

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap(); var o = {}; o[wm] = 12 // desugars to wm.set(o, 12) var a = o[wm]; // desugars to wm.get(o); wm in o // desugars to wm.has(o); delete o[wm] // desugars to wm.delete(o);

You are not showing the desugaring in general:

function get(o,x) { return o[x]; }

must transform to

function get(o,x) { return (x is WeakMap) ? x.get(o) : o[x]; } You're right, I hadn't thought of that.

This is not an acceptable hit for every []-named property access. I intuit (and may be wrong) that even just observing types (weakmap or stringified type) passed to a []-access can be a good indicator of how best []-named property access should be JIT-compiled. You pay the price of the (x is WeakMap) test only the time the JIT warms up. Given what's in current engines, it sounds doable.

If you want to explore this area, I suggest taking a fresh look at strawman:object_model_reformation

# Brendan Eich (12 years ago)

David Bruant wrote:

Le 16/01/2013 19:42, Brendan Eich a écrit :

David Bruant wrote:

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap();
var o = {};
o[wm] = 12 // desugars to wm.set(o, 12)
var a = o[wm]; // desugars to wm.get(o);
wm in o // desugars to wm.has(o);
delete o[wm] // desugars to wm.delete(o); 

You are not showing the desugaring in general:

function get(o,x) { return o[x]; }

must transform to

function get(o,x) { return (x is WeakMap) ? x.get(o) : o[x]; } You're right, I hadn't thought of that.

This is not an acceptable hit for every []-named property access. I intuit (and may be wrong) that even just observing types (weakmap or stringified type) passed to a []-access can be a good indicator of how best []-named property access should be JIT-compiled. You pay the price of the (x is WeakMap) test only the time the JIT warms up. Given what's in current engines, it sounds doable.

No, it's a hit, pure and simple. Engines do various things, SpiderMonkey does semi-static analysis (type inference), but in generic code, you propose to add a ?: to every [] operation. I think that's a non-starter. I invite others (Andreas R. especially) to weigh in.

# Brendan Eich (12 years ago)

Allen Wirfs-Brock wrote:

If you want to explore this area, I suggest taking a fresh look at strawman:object_model_reformation

How does that help? It does not transpose terms of [] and delegate to the left (formerly right) operand.

# David Bruant (12 years ago)

Le 16/01/2013 20:27, Brendan Eich a écrit :

David Bruant wrote:

Le 16/01/2013 19:42, Brendan Eich a écrit :

David Bruant wrote:

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap();
var o = {};
o[wm] = 12 // desugars to wm.set(o, 12)
var a = o[wm]; // desugars to wm.get(o);
wm in o // desugars to wm.has(o);
delete o[wm] // desugars to wm.delete(o); 

You are not showing the desugaring in general:

function get(o,x) { return o[x]; }

must transform to

function get(o,x) { return (x is WeakMap) ? x.get(o) : o[x]; } You're right, I hadn't thought of that.

This is not an acceptable hit for every []-named property access. I intuit (and may be wrong) that even just observing types (weakmap or stringified type) passed to a []-access can be a good indicator of how best []-named property access should be JIT-compiled. You pay the price of the (x is WeakMap) test only the time the JIT warms up. Given what's in current engines, it sounds doable.

No, it's a hit, pure and simple. Engines do various things, SpiderMonkey does semi-static analysis (type inference), but in generic code, you propose to add a ?: to every [] operation.

I don't understand your point. From the JIT presentations I have watched (one per browser vendor except Opera I think) I have understand that JIT compilation allows to compile the right efficient things for the + operator based on proven (type inference) or observed types. For instance, if type information says that the 2 operands of a given + operation are strings, then what's compiled is a string concatenation without checking types. Likewise if for a given binary +, the 2 operands are ints, it's going to be compiled to an addition without the type checks/conversions (maybe a guard based on whether types are proven or observed)

I think the situation is equivalent to if JS had a binary + only for numbers, I was suggesting to use it for string concatenation and you were answering "no, all binary + operations will take a hit". Early engines probably suffered from that, but the JIT thing I've just described has proven that real-life JS code (how does it compare with your "generic code"?) can be optimized up-to-native assuming some JIT warmup cost.

I feel we're in the exact same situation. Only code taking the deoptimization paths would take a hit. That would be code where the same expression "o[p]" can be used with both strings and weakmaps. I don't imagine this becoming the norm.

Am I misunderstanding how JIT compilation using type information works?

I think that's a non-starter. I invite others (Andreas R. especially) to weigh in.

I'm very interested in implementors opinions indeed.

# Brendan Eich (12 years ago)

David Bruant wrote:

Am I misunderstanding how JIT compilation using type information works?

The problem is not just adding another deoptimization path. Not all JITs use only PICs. Lots of optimizations are possible. But adding a ?: to the generic (I meant what I wrote there) path is not going to fly, in my view.

# David Bruant (12 years ago)

Le 16/01/2013 21:19, Brendan Eich a écrit :

David Bruant wrote:

Am I misunderstanding how JIT compilation using type information works?

The problem is not just adding another deoptimization path. Not all JITs use only PICs. Lots of optimizations are possible. But adding a ?: to the generic (I meant what I wrote there) path is not going to fly, in my view.

I still don't understand what you mean by "generic"? Does it mean adding it to the spec algorithm? And I don't understand why it would be making a situation worse than the binary + situation (PIC or not).

# Allen Wirfs-Brock (12 years ago)

On Jan 16, 2013, at 11:28 AM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

If you want to explore this area, I suggest taking a fresh look at strawman:object_model_reformation How does that help? It does not transpose terms of [] and delegate to the left (formerly right) operand.

No, but it presents a more general way to make [ ] extensible. And, a @elementGet/@elementSet method, as defined in the proposal, could indeed choose to do that sort of transposition.

If you wanted to "bake-in" this specific Weakmap inverted implementation (about which I'm obviously still quite skeptical) it could be included in the default fall-back behavior that is used if a @elementGet/@elementSet implementation is not found.

# Brendan Eich (12 years ago)

David Bruant wrote:

Le 16/01/2013 21:19, Brendan Eich a écrit :

David Bruant wrote:

Am I misunderstanding how JIT compilation using type information works?

The problem is not just adding another deoptimization path. Not all JITs use only PICs. Lots of optimizations are possible. But adding a ?: to the generic (I meant what I wrote there) path is not going to fly, in my view. I still don't understand what you mean by "generic"? Does it mean adding it to the spec algorithm?

No, it means any place where PIC overflows due to megamorphism and generic code must be used, or TI fails due to too many types or the "any" type.

And I don't understand why it would be making a situation worse than the binary + situation (PIC or not).

See above. Every generic []-operator usage is taxed.

# Andreas Rossberg (12 years ago)

On 16 January 2013 20:27, Brendan Eich <brendan at mozilla.com> wrote:

David Bruant wrote:

Le 16/01/2013 19:42, Brendan Eich a écrit :

David Bruant wrote:

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap();
var o = {};
o[wm] = 12 // desugars to wm.set(o, 12)
var a = o[wm]; // desugars to wm.get(o);
wm in o // desugars to wm.has(o);
delete o[wm] // desugars to wm.delete(o);

You are not showing the desugaring in general:

function get(o,x) { return o[x]; }

must transform to

function get(o,x) { return (x is WeakMap) ? x.get(o) : o[x]; }

You're right, I hadn't thought of that.

This is not an acceptable hit for every []-named property access.

I intuit (and may be wrong) that even just observing types (weakmap or stringified type) passed to a []-access can be a good indicator of how best []-named property access should be JIT-compiled. You pay the price of the (x is WeakMap) test only the time the JIT warms up. Given what's in current engines, it sounds doable.

No, it's a hit, pure and simple. Engines do various things, SpiderMonkey does semi-static analysis (type inference), but in generic code, you propose to add a ?: to every [] operation. I think that's a non-starter. I invite others (Andreas R. especially) to weigh in.

Actually, I don't see why this should have a measurable impact on performance in practice. The "generic" case is dog-slow for JavaScript anyway, what matters is how easy it is to specialise for the types actually seen at runtime. And there, this would just add yet another row to the (already complex) matrix of cases for receiver/index type pairs that you optimize for. The same might actually be true for symbols, depending on the implementation strategy.

Like any new type or representation, it may cause deoptimization and increased polymorphism, but that's nothing new under the sun, and we are adding plenty of that with ES6.

# Herby Vojčík (12 years ago)

Brendan Eich wrote:

David Bruant wrote:

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap(); var o = {}; o[wm] = 12 // desugars to wm.set(o, 12) var a = o[wm]; // desugars to wm.get(o); wm in o // desugars to wm.has(o); delete o[wm] // desugars to wm.delete(o);

You are not showing the desugaring in general:

function get(o,x) { return o[x]; }

must transform to

function get(o,x) { return (x is WeakMap) ? x.get(o) : o[x]; }

I'd go for genericity here: function get(o,x) { return x.@@isReversedPseudoProperty ? x[o] : o[x]; }

combined with OMR which gives WeakMap x[o] ability, it could be generic and allow other "reversed getters".

# Brendan Eich (12 years ago)

Andreas Rossberg wrote:

Actually, I don't see why this should have a measurable impact on performance in practice. The "generic" case is dog-slow for JavaScript anyway, what matters is how easy it is to specialise for the types actually seen at runtime. And there, this would just add yet another row to the (already complex) matrix of cases for receiver/index type pairs that you optimize for. The same might actually be true for symbols, depending on the implementation strategy.

Probably I'm more sensitive to the "generic" case, which while dog slow still dogs some real-world critical paths (if not fake/lame/old benchmarks).

It all costs, David is proposing yet another cost. Maybe that's my final answer :-|.

Like any new type or representation, it may cause deoptimization and increased polymorphism, but that's nothing new under the sun, and we are adding plenty of that with ES6.

In contrast, private symbols (any symbols, public/unique or private) should benefit from existing property-in-receiver optimizations. Right?

# Andreas Rossberg (12 years ago)

On 17 January 2013 21:08, Brendan Eich <brendan at mozilla.com> wrote:

Andreas Rossberg wrote:

Actually, I don't see why this should have a measurable impact on performance in practice. The "generic" case is dog-slow for JavaScript anyway, what matters is how easy it is to specialise for the types actually seen at runtime. And there, this would just add yet another row to the (already complex) matrix of cases for receiver/index type pairs that you optimize for. The same might actually be true for symbols, depending on the implementation strategy.

Probably I'm more sensitive to the "generic" case, which while dog slow still dogs some real-world critical paths (if not fake/lame/old benchmarks).

It all costs, David is proposing yet another cost. Maybe that's my final answer :-|.

I don't know enough about the internals of other VMs, but at least in V8, the generic case will jump into the C++ runtime (costly) and potentially trickle through hundreds of lines of logic. I think you will have a very hard time constructing even a highly artificial benchmark with which one additional conditional in that logic would be measurable.

Obviously, that doesn't imply that I consider it a good idea... ;)

Like any new type or representation, it may cause deoptimization and increased polymorphism, but that's nothing new under the sun, and we are adding plenty of that with ES6.

In contrast, private symbols (any symbols, public/unique or private) should benefit from existing property-in-receiver optimizations. Right?

Perhaps, perhaps not. For V8, we haven't really thought hard yet about how they fit into the existing representation. They may still end up requiring a case distinction somewhere.

# Claude Pache (12 years ago)

Le 16 janv. 2013 à 09:11, David Bruant <bruant.d at gmail.com> a écrit :

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap(); var o = {}; o[wm] = 12 // desugars to wm.set(o, 12) var a = o[wm]; // desugars to wm.get(o); wm in o // desugars to wm.has(o); delete o[wm] // desugars to wm.delete(o);

<snip>

Hello,

I've just thought of a fundamental flow in the idea of using of weak maps instead of private symbols, which I believe is enough for forcing to forget that idea, at least on a language design level.

(Note: I have not reread all what have been posted on this list on the subject since then, so I am not sure that someone has not already raised that objection.)

Weak maps and private symbols have completely different goals, and conflating the two features into one can (and, as I'll show, will) lead to conflict of interest in designing the API. Weak maps are designed for better memory management, while private symbols are designed for better encapsulation. The key difference of philosophy between the two features is:

  • In weak maps, you need not have access to the value if you don't have access to the key.
  • With private symbols used as property keys, you must not have access to the property value if you don't have access to the symbol.

It is just a happenstance that, in our case "need not" mostly implies "do not". The problem is that it is only "mostly". Indeed:

It is very reasonable to have a -clear() method on WeakMaps, which delete all relations defined by the weak map, in situation where you know that you won't need these mappings any more, but it would be impractical to either enumerate all the keys, or to wipe all references to the weak map. Good for memory management.

On the other hand, you certainly do not want a ".revoke()" method available on every private symbols, which wipe all properties which use that key as private symbol, even on objects to which you are not allowed to have access. Bad for encapsulation.

There is no fundamental problem to use a feature for something very different that it was not designed for. But before sanctioning such a use in a language design, conflict of interest between the two intended use must be carefully considered.

Claude

# David Bruant (12 years ago)

Le 21/01/2013 10:05, Claude Pache a écrit :

Le 16 janv. 2013 à 09:11, David Bruant <bruant.d at gmail.com> a écrit :

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap();
var o = {};
o[wm] = 12 // desugars to wm.set(o, 12)
var a = o[wm]; // desugars to wm.get(o);
wm in o // desugars to wm.has(o);
delete o[wm] // desugars to wm.delete(o);

<snip> Hello,

I've just thought of a fundamental flow in the idea of using of weak maps instead of private symbols, which I believe is enough for forcing to forget that idea, at least on a language design level.

(Note: I have not reread all what have been posted on this list on the subject since then, so I am not sure that someone has not already raised that objection.)

Weak maps and private symbols have completely different goals, and conflating the two features into one can (and, as I'll show, will) lead to conflict of interest in designing the API. Weak maps are designed for better memory management, while private symbols are designed for better encapsulation. The key difference of philosophy between the two features is:

  • In weak maps, you need not have access to the value if you don't have access to the key.

Although I see it's crucial to understanding your point, I don't understand this sentence.

  • With private symbols used as property keys, you must not have access to the property value if you don't have access to the symbol.

It is just a happenstance that, in our case "need not" mostly implies "do not". The problem is that it is only "mostly". Indeed:

It is very reasonable to have a -clear() method on WeakMaps, which delete all relations defined by the weak map, in situation where you know that you won't need these mappings any more, but it would be impractical to either enumerate all the keys

It's not possible at all to enumerate weak map keys. The good reason for that is that the set of keys depends on GC which is not deterministic (2 successive enumeration may randomly result in different enumerations).

or to wipe all references to the weak map. Good for memory management.

Before the clear method, weakmaps had the following property: "you can only modify a weakmap entry if you have the key". This property isn't true anymore with .clear. This may be considered as abusive ambient authority.

On the other hand, you certainly do not want a ".revoke()" method available on every private symbols, which wipe all properties which use that key as private symbol, even on objects to which you are not allowed to have access. Bad for encapsulation.

I agree for private symbols and I think it equally applies to weak maps.

There is no fundamental problem to use a feature for something very different that it was not designed for. But before sanctioning such a use in a language design, conflict of interest between the two intended use must be carefully considered.

I agree with you. I starting exploring the idea with the expectation to get this kind of feedback. I still thinking of weakmaps with the get/set/has/delete interface and the property I mentioned above.

# Claude Pache (12 years ago)

Le 21 janv. 2013 à 11:58, David Bruant <bruant.d at gmail.com> a écrit :

Le 21/01/2013 10:05, Claude Pache a écrit :

Le 16 janv. 2013 à 09:11, David Bruant <bruant.d at gmail.com> a écrit :

Hi,

This is an idea naturally derived of all the current discussions about WeakMaps and Private symbols. The proposal is easily summarized by these lines of code:

var wm = new WeakMap(); var o = {}; o[wm] = 12 // desugars to wm.set(o, 12) var a = o[wm]; // desugars to wm.get(o); wm in o // desugars to wm.has(o); delete o[wm] // desugars to wm.delete(o);

<snip> Hello,

I've just thought of a fundamental flow in the idea of using of weak maps instead of private symbols, which I believe is enough for forcing to forget that idea, at least on a language design level.

(Note: I have not reread all what have been posted on this list on the subject since then, so I am not sure that someone has not already raised that objection.)

Weak maps and private symbols have completely different goals, and conflating the two features into one can (and, as I'll show, will) lead to conflict of interest in designing the API. Weak maps are designed for better memory management, while private symbols are designed for better encapsulation. The key difference of philosophy between the two features is:

  • In weak maps, you need not have access to the value if you don't have access to the key. Although I see it's crucial to understanding your point, I don't understand this sentence.

The basic idea of weak maps is that, as soon as you have lost explicit reference to some key, it can be garbage-collected. In order to achieve that without exposing GC mechanism, you typically avoid to give access to a key or its corresponding value if you are not able to provide explicitly that key. What I want to say, is that this is a mean, not a requirement (but apparently we disagree on that point, see below).

  • With private symbols used as property keys, you must not have access to the property value if you don't have access to the symbol.

It is just a happenstance that, in our case "need not" mostly implies "do not". The problem is that it is only "mostly". Indeed:

It is very reasonable to have a -clear() method on WeakMaps, which delete all relations defined by the weak map, in situation where you know that you won't need these mappings any more, but it would be impractical to either enumerate all the keys It's not possible at all to enumerate weak map keys. The good reason for that is that the set of keys depends on GC which is not deterministic (2 successive enumeration may randomly result in different enumerations).

Instead of "enumerate all the keys", I should have said "explicitly provide all the keys" (without the aid of some inexistent built-in enumeration mechanism).

or to wipe all references to the weak map. Good for memory management. Before the clear method, weakmaps had the following property: "you can only modify a weakmap entry if you have the key". This property isn't true anymore with .clear. This may be considered as abusive ambient authority.

If indeed this property ("you can only modify a weakmap entry if you have the key") is a goal of WeakMaps, my objection is void. Personally, I consider that property as a mean to achieve another goal, namely garbage collection of keys or values without the need of explicit revocation of the association key/value. But this issue is the subject of your next message ("Questioning WeakMap.prototype.clear"), where it will be more properly discussed.