Why ES6 introduced classes yet `Symbol` not to be used with `new`?
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());
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
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()
.
.
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...
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 disallowednew 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).
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 codenew Symbol()
with the expectation that they were creating a Symbol value. This would be a silent bug so we disallowednew 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 toSymbol()
? 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.
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
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.
All of the recorded discussion can be found here:
rwaldron/tc39-notes/blob/master/es6/2013-03/mar-14.md#46-symbols
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.
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 saySymbol
is just an anomaly in the specs because it acts like other primitive constructors but it throws if used asnew Symbol()
.Developers that would've written
new Symbol
are the same that writenew Boolean
ornew Number
andnew 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.
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.
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()
andnew 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 anew Symbol()
when they really meantSymbol()
.
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'.
It's too late for Symbol, but I agree with Alexander.
Anyway, this is my a summary:
Symbol
is not a class andnew Symbol
throwsObject(Symbol())
is possiboe and it's also aninstanceof Symbol
Symbol
cannot be implicitly converted to a string so"" + Symbol()
throwsString(Symbol())
works though{[Symbol()]: value}
is by default enumerable, even if kinda born to be defined on prototypesfor/in
andfor/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
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 onSymbol()
but notnew Symbol()
. Was it to save three characters of typing?