When looked abstractly, both private name and weak maps propose an equivalent feature: a mapping between 2 unforgeable references and a value. Both allow to share a secret with someone assuming the knowledge of 2 unforgeable entities. Both have the interesting property of symmetric non-discoverability:
- Given an object, you can't list all private names
- Given a private name, you can't list all objects which have this name as a property
- Given a weak map, you can't list all objects used as keys
- Given an object, you can't list all weak maps using this object as key
This can be used in WeakMaps to optimize garbage collection. As it turns out, the same thing stands for private names: If nothing holds a reference to a private name, all related slots in objects can be garbage-collected as well.
There are a couple of differences:
- A private property can be made non-configurable & non-writable
- A private name can refer to an accessor property
I claim that these can be reimplemented by overriding the WeakMap API.
I'm open to discussion on whether there are cases that can be implemented with one API and not the other. For the rest of this message, I'll assume that it's the same feature with 2 different syntax.
Unfortunately, private names and WeakMaps (and maps and sets...) both define a special new sort of object to work. What about an API that would allow to associate any 2 objects and bind a "secret" value to the association?
// In one part of the code
var o1 = {},
o2 = {};
var a = Assoc(o1, o2);
a.set(37);
// ...
// In another component which has a reference to both o1 and o2
var myA = Assoc(o1, o2);
myA.get(); // 37
In order to unseal a secret associated with 2 objects, you need a reference to both, that's it. Exactly like with WeakMaps, exactly like with private names.
WeakMap = function(){
var weakMapIdentity = {};
var presence = {};
return {
get: function(o){
return Assoc(weakMapIdentity, o).get();
},
set: function(o, val){
Assoc(presence, o).set(true);
return Assoc(weakMapIdentity, o).set(val);
},
has: function(o){
return !!Assoc(presence, o).get();
},
delete = function(o){
Assoc(presence, o).set(undefined);
}
}
}
I feel that "presence" shouldn't be there and the WeakMap API should be kept to get and set.
Some properties:
- Assoc() === Assoc // why waste a reference? :-p
- Assoc(o) !== o
- Assoc(o) === Assoc(o)
- Assoc(Assoc(o)) === Assoc(o)
- Assoc(o, o) === Assoc(o)
- Assoc(o1, o2) === Assoc(o2, o1)
The Assoc API could be extended with any number of arguments. The resulting object represents the association of all objects passed as arguments.
- Assoc(o1, o2, o3) === Assoc(Assoc(o1, o2), o3) === Assoc(o1, Assoc(o2, o3))
- ...and these applies to any number of arguments.
Long story short, the Assoc function only cares about whether or not you pass it the right references to unseal the secret. And you should not care about passing the objects in the "right order". If I want to keep a secret from you, I'll create an unforgeable reference; I won't try to "obscure" it by not telling you the order in which you should pass the objects to Assoc
Of course, since there is no discoverability of associations, all good garbage collection optimizations are kept.
With association objects, I think we can have a uniform base to play with objects without having to invent new sorts of objects (WeakMaps, Sets, Maps, etc.) while keeping the possibility to implement (hopefully as efficiently) these abstractions.