Why ES6 introduced classes yet `Symbol` not to be used with `new`?

# /#!/JoePea (8 years ago)

It seems like new Symbol() would be inline with the introduction of classes. Why was it chosen not to be constructible? Seems like it would make sense to throw an error on Symbol() but not new Symbol(). Was it to save three characters of typing?

# Oriol Bugzilla (8 years ago)

Probably, because symbols are primitive values, and constructors only create and initialize objects.

So you are supposed to call it as a function instead of as a a constructor.

That said, it might make sense for new Symbol() to return a symbol object. But instead, you can use


Object(Symbol());

# Domenic Denicola (8 years ago)

Symbol is not a class.

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of /#!/JoePea Sent: Sunday, August 14, 2016 18:31 To: es-discuss <es-discuss at mozilla.org>

Subject: Why ES6 introduced classes yet Symbol not to be used with new?

It seems like new Symbol() would be inline with the introduction of classes. Why was it chosen not to be constructible? Seems like it would make sense to throw an error on Symbol() but not new Symbol(). Was it to save three characters of typing?

/#!/JoePea

# Allen Wirfs-Brock (8 years ago)

Because, to be consistent with Number/String/Boolean you would expect new Symbol() to create a Symbol wrapper object. But we anticipated that if new Symbol was allowed many devs (who lacked an understanding of the difference between primitive values and wrapper objects for primitive values) would code new Symbol() with the expectation that they were creating a Symbol value. This would be a silent bug so we disallowed new Symbol().

.

# Domenic Denicola (8 years ago)

I believe, but am not sure, that we also decided we would follow that pattern for any future primitive types, since in general constructing wrapper objects is a bad idea. (I want to say that wrapper objects themselves are a bad idea, but I think the conclusion was more subtle than that... they are an important part of the semantics, it's just unfortunate that they're so easy to create.)

If some enterprising person wants to dig through the meeting notes, there might be some hints there...

# Kris Siegel (8 years ago)

Interesting.

Because, to be consistent with Number/String/Boolean you would expect `new

Symbol()` to create a Symbol wrapper object.

Currently Symbol is the only primitive that can't be converted to a string through the use of the + operator, so why the consistency in one place and the lack thereof in another? I understand there isn't really a meaningful representation of Symbol() as a string but I didn't see any particular reason in my cursory look at the past notes for it to throw an exception so I've been curious.

But we anticipated that if new Symbol was allowed many devs (who lacked

an understanding of the difference between primitive values and wrapper objects for primitive values) would code new Symbol() with the expectation that they were creating a Symbol value. This would be a silent bug so we disallowed new Symbol().

Forgive me for the ignorance but what kind of bug would this introduce? Since Symbol() is already an oddball compared to all other built-in objects and primitives would it have been so bad to simply make new Symbol() equate to Symbol()? I'm not sure you'll get developers to understand the difference between primitives and wrapper objects (still haven't found one yet who understands this in my inner-circle of JS devs that I know at least).

# Claude Pache (8 years ago)

Le 15 août 2016 à 07:33, Kris Siegel <krissiegel at gmail.com> a écrit :

Interesting.

Because, to be consistent with Number/String/Boolean you would expect new Symbol() to create a Symbol wrapper object.

Currently Symbol is the only primitive that can't be converted to a string through the use of the + operator, so why the consistency in one place and the lack thereof in another? I understand there isn't really a meaningful representation of Symbol() as a string but I didn't see any particular reason in my cursory look at the past notes for it to throw an exception so I've been curious.

In short, preventing implicit conversion to string is for avoiding silent bugs. Typically, in situation where you expect a string, such as:

foo[somePrefix_' + s] = bar[s]

This is not an inconsistency at the language level: implicit conversion to string is done using .toString() or .@@toPrimitve("string"), which has never been guaranteed to succeed. Example:

var o = Object.create(null)
o + "" // will throw a TypeError

But we anticipated that if new Symbol was allowed many devs (who lacked an understanding of the difference between primitive values and wrapper objects for primitive values) would code new Symbol() with the expectation that they were creating a Symbol value. This would be a silent bug so we disallowed new Symbol().

Forgive me for the ignorance but what kind of bug would this introduce?

Examples:

var s = new Symbol
typeof s // "object", not "symbol"

switch (typeof s) {
case "symbol": // will not match
}

and:

var s = new Symbol
var o = { }
o[s] = 42 // the wrapper object is converted to a primitive, 
var s2 = Object.getOwnPropertySymbols(o)[0]
s2 == s // true
s2 === s // false

Since Symbol() is already an oddball compared to all other built-in objects and primitives would it have been so bad to simply make new Symbol() equate to Symbol()? I'm not sure you'll get developers to understand the difference between primitives and wrapper objects (still haven't found one yet who understands this in my inner-circle of JS devs that I know at least).

That would introduce an irregularity in the language, for new Foo has always been allowed to return an object only, not a primitive. The fact there is some lack of understanding among developers is not an excuse for increasing the confusion with inconsistent semantics.

The alternative design would have been to specify symbols as true objects rather than primitives. I recall that that alternative has been considered and discussed during the conception of ES6. You have to dig through the archives in order to find why one design was chosen over the other. The only thing I recall is that it was not a trivial decision.

# Andrea Giammarchi (8 years ago)

FWIW, if Object(Symbol()) instanceof Symbol is true, and it is, we can say Symbol is just an anomaly in the specs because it acts like other primitive constructors but it throws if used as new Symbol().

Developers that would've written new Symbol are the same that write new Boolean or new Number and new String.

If these people don't put minimal effort to better learn/use the programming language I wonder why the rest of the entire community should be penalised with "quirks" like Symbol is.

Throw at everything and then, or don't ... and keep it consistent in both good or bad expectations. I know it's too late for Symbol but I hope there won't be other inconsistent primitives/classes added to the lang in the future.

Best

# Andrea Giammarchi (8 years ago)

just to clarify:

I hope there won't be other inconsistent primitives/classes added to the

lang in the future.

meaning: I'd rather throw at every attempt to new Boolean/Number/String too as amend in the future if "primitives gotta primitive" is the idea and Symbol throwing was to help developers.

# Rick Waldron (8 years ago)
# Allen Wirfs-Brock (8 years ago)

On Aug 14, 2016, at 11:33 PM, Claude Pache <claude.pache at gmail.com> wrote:

The alternative design would have been to specify symbols as true objects rather than primitives. I recall that that alternative has been considered and discussed during the conception of ES6. You have to dig through the archives in order to find why one design was chosen over the other. The only thing I recall is that it was not a trivial decision.

The "symbols are true objects” approach was fully spec’ed in ES6 drafts before ultimately being abandoned.

As Claude says, the history is all in the archives.

# Allen Wirfs-Brock (8 years ago)

On Aug 15, 2016, at 1:27 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

FWIW, if Object(Symbol()) instanceof Symbol is true, and it is, we can say Symbol is just an anomaly in the specs because it acts like other primitive constructors but it throws if used as new Symbol().

Developers that would've written new Symbol are the same that write new Boolean or new Number and new String.

If these people don't put minimal effort to better learn/use the programming language I wonder why the rest of the entire community should be penalised with "quirks" like Symbol is.

The difference is that unlike Number/String/Boolean, there is not a literal syntax for creating Symbol values. For those primitive types that have a literal representation, it is very rare to ever need to explicitly instantiate a wrapper object via new and hence the there is little chance confusing the designation of a primitive value with instantiating a wrapper.

Because Symbol does not have a syntactic literal form, some sort of invocation is required to acquire a new unique primitive Symbol value. That is where you get the potential for confusion between Symbol() and new Symbol(). And minimizing this sort of mistake is not just a matter of needing “better learning”. Even an experts can have momentary mental slips and type a new Symbol() when they really meant Symbol().

In the future, I would expect any new primitive types that have literals to follow the precedent of Number/String/Boolean and any that do not to follow the precedent of Symbol.

# Andrea Giammarchi (8 years ago)

Symbol.create() together with others Symbol.for() and company would've been a "least surprise" choice with a Symbol spec'd like Math, JSON or other global objects ... but yeah, we have a precedent now, so I guess in the future will be like that.

# Alexander Jones (8 years ago)

On 15 August 2016 at 19:20, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Because Symbol does not have a syntactic literal form, some sort of invocation is required to acquire a new unique primitive Symbol value. That is where you get the potential for confusion between Symbol() and new Symbol(). And minimizing this sort of mistake is not just a matter of needing “better learning”. Even an experts can have momentary mental slips and type a new Symbol() when they really meant Symbol().

Honestly, I think this kind of basic type error is something which is so trivially catchable at a very early stage when you have even a semblance of static analysis. new Symbol() should give you an object of class Symbol, and Symbol() should give you a symbol - and TypeScript even without annotations is going to give you a pretty strong warning. IMO it's a bit ugly to break the pattern of primitive wrappers being newable for the sake of saving programming errors, when the analyzer can do a much more complete job anyway.

let s1 = String("banana"); // string
let s2 = new String("banana"); // object
let o = {banana: 2};
o[s1];
o[s2]; // red squiggly: An index expression argument must be of type
'string', 'number', 'symbol', or 'any'.
# Andrea Giammarchi (8 years ago)

It's too late for Symbol, but I agree with Alexander.

Anyway, this is my a summary:

  • Symbol is not a class and new Symbol throws
  • Object(Symbol()) is possiboe and it's also an instanceof Symbol
  • Symbol cannot be implicitly converted to a string so "" + Symbol() throws
  • String(Symbol()) works though
  • {[Symbol()]: value} is by default enumerable, even if kinda born to be defined on prototypes
  • for/in and for/of won't show it though ... but ...
  • Object.assign will copy them over

As a developer, I would've stick with a basic String variant that inherits toString via Object prototype and by default is not enumerable, at least the latter part would've been the only quirk, but a useful one.

Instead, all the effort put to make it right became quite confusing so that, from time to time, somebody will ask about new Symbol VS Object, about Object.assign symbols clashes (iterators and others) and the fact JSON silently ignores them as if these are undefined and these are not even passed to the replacer like other enumerable keys.

Yes, there is history, discussions and reasons behind Symbol done in this way, but I bet we can all agree it didn't came out exactly as a "unicorn".

Best