Proposal: new Symbol(obj)

# Jeremy Martin (12 years ago)

In brief: allow Symbol's to be constructed with a single parameter, with the following behavior:

> var obj = {};
undefined

> new Symbol({}) === new Symbol({})
false

> new Symbol(obj) === new Symbol(obj)
true

Motivation: the ability to construct equal Symbols gives us the necessary building blocks to build custom maps with semantics very similar to Simple Maps:

function ObjectMap() {
    this.map = {};
}

SimpleMap.prototype.set = function(objKey, val) {
    this.map[new Symbol(objKey)] = val;
};

SimpleMap.prototype.get = function(objKey) {
    return this.map[new Symbol(objKey)];
};

At a surface level, this seems more novelty than anything else, but I think it's a useful primitive for building more complex and robust features on top of Symbols. Thoughts?

# Allen Wirfs-Brock (12 years ago)

You can use a WeakMap to build your own object-selected Symbol factory:

let known = new WeakMap;

function ObjSymFactory(obj) {
   //handle obj === undefined however you want
   let sym = known.get(obj);
   if (!sym) {
      sym = new Symbol;
      known.set(obj,sym);
   }
   return sym;
}
# Jeremy Martin (12 years ago)

Good point, that's definitely a usable solution (also a better representation of what I was attempting to describe).

I'd still be interested in a less-verbose/more-efficient approach using the Symbol constructor, but it may not be a common enough scenario to justify it when a workaround does exist.

# K. Gadd (12 years ago)

I would welcome (with fanfare and parades) a new Symbol(obj) that worked for strings and integers. Such is not possible using the WeakMap shim (you'd have to detect the type of the value and have multiple dictionaries, or something, and you'd leak the symbols forever...)

Of course, what that means is I'm asking for weakly cached symbols with referential identity, which means it would allow detecting garbage collections, so this is probably around the point where people chime in and say it's not possible. Oh well. :(

# Jeremy Martin (12 years ago)

My expectation would be that...

(a === b) === (new Symbol(a) === new Symbol(b))

I.e., new Symbol(a) === new Symbol(b) iff a === b. This satisfies the strings/integers scenario, but, of course, fails your WeakMap garbage collection semantics. You need WeakSymbolMaps (+ this proposal) :)

# Andreas Rossberg (12 years ago)

On 12 July 2013 22:29, Jeremy Martin <jmar777 at gmail.com> wrote:

My expectation would be that...

(a === b) === (new Symbol(a) === new Symbol(b))

Nit: you probably either mean

(a === b) >= (new Symbol(a) === new Symbol(b))

or

(Object.is(a, b)) === (new Symbol(a) === new Symbol(b))

In any case, I'd also say that weak maps are good enough for your use case.

# Jeremy Martin (12 years ago)
(a === b) >= (new Symbol(a) === new Symbol(b))

Not sure I follow... so, I either I don't agree or I don't understand :). I'm having to dig deep to remember my math vocab here, but I think it may be most correct to say that the Symbol constructor, when passed an argument, should be injective. That is,

  1. let F = new Symbol
  2. if a = b, then F(a) = F(b)
  3. if a != b, then F(a) != F(b)

In any case, I'd also say that weak maps are good enough for your use case.

In some (most?) cases, but not all. There's already a consensus that the garbage collection semantics of WeakMaps aren't always appropriate 2. By parameterizing the Symbol constructor, developers can create custom map/set types without the overhead of a "Symbol Factory" (as previously suggested by Allen). I believe this would be a useful building block for interesting and innovative custom types.

# Andreas Rossberg (12 years ago)

On 15 July 2013 15:49, Jeremy Martin <jmar777 at gmail.com> wrote:

Not sure I follow... so, I either I don't agree or I don't understand :). I'm having to dig deep to remember my math vocab here, but I think it may be most correct to say that the Symbol constructor, when passed an argument, should be injective [1].

I agree, but the problem is that JavaScript's === is not an equivalence relation, due to the dreaded NaN !== NaN that IEEE invented in some delirium. So you cannot define injectivity based on it. You merely get an implication for the above, which is what the >= was supposed to encode. Object.is OTOH implements a proper equivalence relation, i.e. a "=" in the mathematical sense. It only differs from === by having a sane semantics for NaN.

But as I said, I was merely picking a nit.

# Mark S. Miller (12 years ago)

On Mon, Jul 15, 2013 at 7:03 AM, Andreas Rossberg <rossberg at google.com>wrote:

It only differs from === by having a sane semantics for NaN.

And a tighter equivalence for -0 vs 0

# Jeremy Martin (12 years ago)

so, I either I don't agree or I don't understand

It was the latter - I understand and agree now :)

# Allen Wirfs-Brock (12 years ago)

On Jul 15, 2013, at 6:49 AM, Jeremy Martin wrote:

In some (most?) cases, but not all. There's already a consensus that the garbage collection semantics of WeakMaps aren't always appropriate 2. By parameterizing the Symbol constructor, developers can create custom map/set types without the overhead of a "Symbol Factory" (as previously suggested by Allen). I believe this would be a useful building block for interesting and innovative custom types.

An implementation of your proposal is going to have to have to internally use some wort of weak-keyed table, in practice an implementation would probably use the same GC mechanisms that are there to support WeakMap. So, I doubt there would be much performance difference between a built-in and a roll-your-own implementation.

# K. Gadd (12 years ago)

The builtin could have weak values (i.e. Symbol instances expire when no longer referenced by JS) instead of weak keys, which is not something we can currently express in JS. This would also make it possible to use strings/integers/floats as Symbol keys without leaking those Symbol instances forever. This is not something we can express in JS either.

Both of these behaviors would not, as I understand it, be directly observable since keeping an old Symbol instance around (to compare with) would prevent the cached one from being collected. On the other hand, I think if you were to resynthesize the number/string used as a symbol key and make a new symbol, that might allow you to observe whether the symbol had been collected. This doesn't seem like a huge problem to me but I forget the exact reasoning why weak references were unacceptable in the past; maybe it still applies to this.