How primitive are Symbols? Bignums? etc

# Mark S. Miller (11 years ago)

Given that s is a Symbol and b is a Bignum, is

s === Object(s)

?

Is

b === Object(b)

?

The reason I ask is that x === Object(x) is often used to distinguish primitive values from objects. The other thing that's often used is typeof, but these uses of typeof usually assume that the full range of possible answers is already known. If we're going to be expanding the typeof answers, then we need guidance on how to write this test now (es5) such that it stays robust.

# Jeremy Martin (11 years ago)

s === Object(s)

This should throw a TypeError. In the case of a Symbol, Object(*value*) results in ToObject(*value*) 1. And, in the case of Symbol, ToObject should throw a TypeError 2.

# Mark S. Miller (11 years ago)

I see. That's unpleasant. In ES5, Object(x) can never throw, and so the code paths using x === Object(x) are not prepared for a throw. Much old code will break.

# André Bargull (11 years ago)

Allen (cc-ed) changed symbols back to objects in draft rev 16 (ecmascript#1546#c2), so I guess Object(x) will still work in ES6 to test for object types.

# Jeremy Martin (11 years ago)

That's unpleasant. [...] Much old code will break.

Indeed. I hadn't actually noticed that change until just now. It looks like ES6 code can take advantage of Object.isObject(), which seems to delegate the work to Type(x) 1. It wasn't overwhelmingly clear to me, but I would assume Object.isObject('foo') === false and Object.isObject(new String('foo')) === true.

Has anyone surveyed/looked into what the fallout of throwing on stuff like Object(undefined) will be?

# Mark S. Miller (11 years ago)

Object(undefined) would still not throw: people.mozilla.org/~jorendorff/es6-draft.html#sec-15.2.1.1 step 1

# Jeremy Martin (11 years ago)

Ahh, thanks. Somehow I read that as Object(undefined) -> ObjectCreate(undefined) -> throw, but that's not the case.

# Allen Wirfs-Brock (11 years ago)

On Jul 15, 2013, at 8:35 AM, André Bargull wrote:

Allen (cc-ed) changed symbols back to objects in draft rev 16 (ecmascript#1546#c2), so I guess Object(x) will still work in ES6 to test for object types.

Correct, Symbols as primitive values were just causing too many issues. Essentially, everyplace in the spec. that needs an object had to be updated to explicit deal with Symbols. That certainly isn't a pattern we want to follow in the future if add new "value types" such as bignums. It's much cleaner to freeze the set of primitive types and make all future value types (including Symbols) objects. just as it would have been even cleaner if everything was an object and there were not "primitive types".

Regarding, typeof. The right way to look at it is that the set of results that correspond to non-object types will be fixed and includes only ("undefined", "null", "number", "string", "boolean"). All other typeof values correspond of objects (where an object is a value that support the ES internal MOP).

# Andreas Rossberg (11 years ago)

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

s === Object(s)

This should throw a TypeError. In the case of a Symbol, Object(value) results in ToObject(value) 1. And, in the case of Symbol, ToObject should throw a TypeError 2.

I'm not sure how up-to-date the spec is on that. In any case, it's not what I would expect either (or what's currently implemented in V8, for that matter). My assumption was that Object(symbol) works analogous to Object(string) and returns a wrapper object. That is, Object(Symbol()) is equivalent to new Symbol.

# Dean Landolt (11 years ago)

On Mon, Jul 15, 2013 at 12:24 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Regarding, typeof. The right way to look at it is that the set of results that correspond to non-object types will be fixed and includes only ("undefined", "null", "number", "string", "boolean"). All other typeof values correspond of objects (where an object is a value that support the ES internal MOP).

I'm very surprised to see "null" in this list, and not "function" -- a typeof typo I hope?

# Mark S. Miller (11 years ago)

typeof x === 'function' and typeof x === 'object' are the current indications of non-primitive types. What Allen is saying is that this set may expand, but the typeof answers that indicate primitive value types will not. Allen was enumerating the non-expanding set of primitive-value answers.

# Andreas Rossberg (11 years ago)

On 15 July 2013 18:24, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jul 15, 2013, at 8:35 AM, André Bargull wrote:

Allen (cc-ed) changed symbols back to objects in draft rev 16 (ecmascript#1546#c2), so I guess Object(x) will still work in ES6 to test for object types.

Correct, Symbols as primitive values were just causing too many issues.

Oh. I wasn't aware of this. Is this just a spec language change, or is it a semantic change? If the latter, then I have to disagree with the change.

Essentially, everyplace in the spec. that needs an object had to be updated to explicit deal with Symbols.

Allen, can you elaborate where symbols introduced cases that did not already have to be handled for other primitive types?

# Allen Wirfs-Brock (11 years ago)

On Jul 15, 2013, at 9:35 AM, Dean Landolt wrote:

On Mon, Jul 15, 2013 at 12:24 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Regarding, typeof. The right way to look at it is that the set of results that correspond to non-object types will be fixed and includes only ("undefined", "null", "number", "string", "boolean"). All other typeof values correspond of objects (where an object is a value that support the ES internal MOP).

I'm very surprised to see "null" in this list, and not "function" -- a typeof typo I hope?

Sorry, "null" should be in the list, and is the one legacy value that muddles typeof.

functions are objects, not primitive values.

# Allen Wirfs-Brock (11 years ago)

On Jul 15, 2013, at 9:38 AM, Mark S. Miller wrote:

typeof x === 'function' and typeof x === 'object' are the current indications of non-primitive types. What Allen is saying is that this set may expand, but the typeof answers that indicate primitive value types will not. Allen was enumerating the non-expanding set of primitive-value answers.

Yes, this better expresses what I meant

# Allen Wirfs-Brock (11 years ago)

On Jul 15, 2013, at 9:40 AM, Andreas Rossberg wrote:

Oh. I wasn't aware of this. Is this just a spec language change, or is it a semantic change? If the latter, then I have to disagree with the change.

The is primarily an internal spec. change. Many internal operations within the spec. require objects as parameters. This required inserting inserting explicit guards in p=many places within the specification and remembering to include them in new algorithms. At the March meeting you objected to the "two pages of specification" required to define Symbols as exotic objects. It turned out that those two pages were fair simpler and less intrusive than the all the individual spec. changes (and ongoing additions) that were needed to support symbols as primitive values.

User visible semantics comes down to whether or not there is a Symbol wrapper object. As far as I can tell, tell from the notes, there was no consensus WRT Symbol wrappers record in March and when I tried to convert to Symbols as primitive values in the spec. I don't provide such wrappers. Instead, I made ToObject throw for symbols values.

If you have wrapper objects, then you have visible semantics such as:

let s = Symbol();
console.log(Object(s) === Object(s));  //false, because each call to Object produces a new wrapper object
console(s === Object(s));   //false, because each call to Object produces a new wrapper object

If you have symbols as primitive values, but no wrappers you get:

console.log(Object(s) === Object(s));  //TypeError
console(s === Object(s));   //TypeError

If you have symbols as exotic objects you get:

console.log(Object(s) === Object(s));  //true
console(s === Object(s));   //true

Because ES5 added auto-wrapping of primitive values within PutValue/GetValue, primitive values already act as if they were objects in most situations and except for the identify complications numbers/strings/booleans values can generally be used and reasoned about as if they were instances of Number/String/Boolean. We really should avoid adding new primitive types and wrapper objects. Value objects are the way to go, starting with Symbol.

I'd be interested in hearing how this makes any difference to you from an implementation perspective. Even when symbols are specified as exotic objects you can still encode them as immediate values, just like you would a SmallInteger in Smalltalk. It's only when actual object MOP operations are applied to them that you should have to do any special casing but these are generally the same situations where you would have to auto-wrap primitive values.

I would be interested in hearing if there are places in your implementation where specifying symbols as exotic objects will make an actual performance difference when using symbols as property keys.

Allen, can you elaborate where symbols introduced cases that did not already have to be handled for other primitive types?

Every place that did a ToObject, or equivalent. I'm pushing out a new spec. draft today or tomorrow and you can see the changes there.

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

On Jul 15, 2013, at 8:35 AM, André Bargull wrote:

Allen (cc-ed) changed symbols back to objects in draft rev 16 (ecmascript#1546#c2), so I guess Object(x) will still work in ES6 to test for object types.

Correct, Symbols as primitive values were just causing too many issues. Essentially, everyplace in the spec. that needs an object had to be updated to explicit deal with Symbols. That certainly isn't a pattern we want to follow in the future if add new "value types" such as bignums. It's much cleaner to freeze the set of primitive types and make all future value types (including Symbols) objects. just as it would have been even cleaner if everything was an object and there were not "primitive types".

+1.

Regarding, typeof. The right way to look at it is that the set of results that correspond to non-object types will be fixed and includes only ("undefined", "null", "number", "string", "boolean"). All other typeof values correspond of objects (where an object is a value that support the ES internal MOP).

(Regrets on lack of "null" typeof-result, of course.)

I'm updating my int64/uint64 experimental patch to return "int64" and "uint64" typeof-types, to uphold these invariants:

typeof x == typeof y && x == y <=> x === y

If typeof 0L and 0UL were "object", this would fail, but we want 0L == 0UL. This came up with decimal in the ES5 era, where 0m == 0 but 0m !== 0. It's generally a problem, not just for 0 but for many pairs of numeric types subsuming a subset of the Integers where the values in common should equate by == but not ===.

The (x === Object(x)) test evaluates to true for value objects in this proposal, though. This may break code looking for "primitives" but we need to see what such code expects. Is it filtering out the legacy typeof-result primitives (plus "null"), trying to find values for which typeof currently returns "object" or "function"? If so, I don't see a problem: int64, bignum, etc. are not legacy primitives. Is this test looking for objects that are their own wrappers? Again all is well, unless "mutable wrapper" is assumed -- but that's not safe in the ES5 era to assume, anyway.

# Axel Rauschmayer (11 years ago)

The (x === Object(x)) test evaluates to true for value objects in this proposal, though. This may break code looking for "primitives" but we need to see what such code expects. Is it filtering out the legacy typeof-result primitives (plus "null"), trying to find values for which typeof currently returns "object" or "function"? If so, I don't see a problem: int64, bignum, etc. are not legacy primitives. Is this test looking for objects that are their own wrappers? Again all is well, unless "mutable wrapper" is assumed -- but that's not safe in the ES5 era to assume, anyway.

The most frequent use case I’ve encountered: does the value have a prototype (i.e., will Object.getPrototypeOf() work)?

I’m assuming that value objects will have a prototype, accessible via Object.getPrototypeOf (?)

Axel

# Brendan Eich (11 years ago)

Axel Rauschmayer wrote:

The (x === Object(x)) test evaluates to true for value objects in this proposal, though. This may break code looking for "primitives" but we need to see what such code expects. Is it filtering out the legacy typeof-result primitives (plus "null"), trying to find values for which typeof currently returns "object" or "function"? If so, I don't see a problem: int64, bignum, etc. are not legacy primitives. Is this test looking for objects that are their own wrappers? Again all is well, unless "mutable wrapper" is assumed -- but that's not safe in the ES5 era to assume, anyway.

The most frequent use case I’ve encountered: does the value have a prototype (i.e., will Object.getPrototypeOf() work)?

I’m assuming that value objects will have a prototype, accessible via Object.getPrototypeOf (?)

Yes, they are after all value objects :-P.

js> Object.getPrototypeOf(0UL)

0UL

# Andreas Rossberg (11 years ago)

On 15 July 2013 19:44, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

The is primarily an internal spec. change. Many internal operations within the spec. require objects as parameters. This required inserting inserting explicit guards in p=many places within the specification and remembering to include them in new algorithms. At the March meeting you objected to the "two pages of specification" required to define Symbols as exotic objects. It turned out that those two pages were fair simpler and less intrusive than the all the individual spec. changes (and ongoing additions) that were needed to support symbols as primitive values.

I'm afraid I still don't see how this is the case (the draft diffs are not particularly easy to read). Almost everything should be handled by ToObject creating a wrapper object in the traditional places, shouldn't it? What are the contexts where (a) an object is required, but (b) a symbol would behave differently from existing primitives?

User visible semantics comes down to whether or not there is a Symbol wrapper object. As far as I can tell, tell from the notes, there was no consensus WRT Symbol wrappers record in March and when I tried to convert to Symbols as primitive values in the spec. I don't provide such wrappers. Instead, I made ToObject throw for symbols values.

OK, that might explain the difficulty you faced. My understanding of the March agreement certainly was that there is a wrapper object, consistent with other primitive types. And I'm pretty sure that that makes the spec quite simple and regular.

We really should avoid adding new primitive types and wrapper objects. Value objects are the way to go, starting with Symbol.

I'd argue for the contrary, namely that we should avoid artificially cramping more inappropriate concepts into the notion of 'object'! These are not objects by any useful definition of the word, despite the MOP being rich enough to support them as degenerate cases.

I'd be interested in hearing how this makes any difference to you from an implementation perspective. Even when symbols are specified as exotic objects you can still encode them as immediate values, just like you would a SmallInteger in Smalltalk. It's only when actual object MOP operations are applied to them that you should have to do any special casing but these are generally the same situations where you would have to auto-wrap primitive values.

The difference is that with real primitives + wrapper objects every respective operation has a single place where it does the ToObject conversion (which exists already), and downstream you can easily make it an invariant that what you've got is an ordinary object with an ordinary object representation. No special casing required.

If, on the other hand, you make new primitives pseudo-objects, but still want to represent them efficiently, then all parts that deal with objects now also have to deal with those denormal representations. You say "only actual MOP operations", but surely there are several times more occurrences of those than of ToObject. It involves the majority of language operations, and in contemporary implementations, every one of them potentially has a dozen or more possible implementations.

Alternatively, you could introduce two different representations for these pseudo-objects and normalise their representation to a real object wherever you perform ToObject. That is, you essentially introduce the wrapper internally in the implementation. But then you have the dual problem: because this has to be transparent, now all contexts that expect a proper symbol also have to deal with wrapped symbols (and probably have to reverse the normalisation). And those are quite a few as well. Likewise, you have to special-case equality, maps, and similar generic operations.

Whichever path you take, artificially unifying two different concepts that have completely disjoint uses and requirements is not going to make anything simpler.

All this may be a worthwhile price to pay if it actually added any value to the language. But I don't see a notable benefit for programmers either. Am I missing something?

# Allen Wirfs-Brock (11 years ago)

On Jul 15, 2013, at 10:30 PM, Brendan Eich wrote:

Axel Rauschmayer wrote:

The (x === Object(x)) test evaluates to true for value objects in this proposal, though. This may break code looking for "primitives" but we need to see what such code expects. Is it filtering out the legacy typeof-result primitives (plus "null"), trying to find values for which typeof currently returns "object" or "function"? If so, I don't see a problem: int64, bignum, etc. are not legacy primitives. Is this test looking for objects that are their own wrappers? Again all is well, unless "mutable wrapper" is assumed -- but that's not safe in the ES5 era to assume, anyway.

The most frequent use case I’ve encountered: does the value have a prototype (i.e., will Object.getPrototypeOf() work)?

I’m assuming that value objects will have a prototype, accessible via Object.getPrototypeOf (?)

Yes, they are after all value objects :-P.

js> Object.getPrototypeOf(0UL) 0UL

In the latest spec. draft, Object.getPrototypeOf(new Symbol) returns null because that is what the [[GetInheritance]] MOP operation produces for exotic symbol objects. That's because symbols symbols aren't supposed to have any observable properties.

[[GetInhertance]] would do something else for value objects that actually exposed inherited properties.

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

On Jul 15, 2013, at 10:30 PM, Brendan Eich wrote:

Axel Rauschmayer wrote:

The (x === Object(x)) test evaluates to true for value objects in this proposal, though. This may break code looking for "primitives" but we need to see what such code expects. Is it filtering out the legacy typeof-result primitives (plus "null"), trying to find values for which typeof currently returns "object" or "function"? If so, I don't see a problem: int64, bignum, etc. are not legacy primitives. Is this test looking for objects that are their own wrappers? Again all is well, unless "mutable wrapper" is assumed -- but that's not safe in the ES5 era to assume, anyway. The most frequent use case I’ve encountered: does the value have a prototype (i.e., will Object.getPrototypeOf() work)?

I’m assuming that value objects will have a prototype, accessible via Object.getPrototypeOf (?) Yes, they are after all value objects :-P.

js> Object.getPrototypeOf(0UL) 0UL

In the latest spec. draft, Object.getPrototypeOf(new Symbol) returns null because that is what the [[GetInheritance]] MOP operation produces for exotic symbol objects. That's because symbols symbols aren't supposed to have any observable properties.

[[GetInhertance]] would do something else for value objects that actually exposed inherited properties.

As it should:

js> Object.getPrototypeOf(0UL) === uint64.prototype

true

for toString and valueOf at least.

# Allen Wirfs-Brock (11 years ago)

On Jul 16, 2013, at 4:34 AM, Andreas Rossberg wrote:

On 15 July 2013 19:44, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

The is primarily an internal spec. change. Many internal operations within the spec. require objects as parameters. This required inserting inserting explicit guards in p=many places within the specification and remembering to include them in new algorithms. At the March meeting you objected to the "two pages of specification" required to define Symbols as exotic objects. It turned out that those two pages were fair simpler and less intrusive than the all the individual spec. changes (and ongoing additions) that were needed to support symbols as primitive values.

I'm afraid I still don't see how this is the case (the draft diffs are not particularly easy to read). Almost everything should be handled by ToObject creating a wrapper object in the traditional places, shouldn't it? What are the contexts where (a) an object is required, but (b) a symbol would behave differently from existing primitives?

User visible semantics comes down to whether or not there is a Symbol wrapper object. As far as I can tell, tell from the notes, there was no consensus WRT Symbol wrappers record in March and when I tried to convert to Symbols as primitive values in the spec. I don't provide such wrappers. Instead, I made ToObject throw for symbols values.

OK, that might explain the difficulty you faced. My understanding of the March agreement certainly was that there is a wrapper object, consistent with other primitive types. And I'm pretty sure that that makes the spec quite simple and regular.

Yes, wrappers would make it easier, but my take away from the meeting and subsequent discussion was that we didn't want to add any more observable wrappers.

We really should avoid adding new primitive types and wrapper objects. Value objects are the way to go, starting with Symbol.

I'd argue for the contrary, namely that we should avoid artificially cramping more inappropriate concepts into the notion of 'object'! These are not objects by any useful definition of the word, despite the MOP being rich enough to support them as degenerate cases.

Clearly I disagree. In ES and other polymorphic object-based languages, objects are really the basis for uniformity of references. Any abstraction can be represented as an object and any operation can be manifested as method invocations upon an object.

It is the ES primitive types that introduce non-uniformity that either needs to be special cased or partially hidden behind hacks such as automatic conversion to wrapper objects. Clearly at an implementation level you want to have optimized representations of certain kinds of entities. You can get there two different ways. You can expose the optimized representation as "primitive types" that requires user level special casing or you can uniformly expose everything as an "object" and let the implementation opaquely use special case representations where it suits it.

It seems clear to me, that if you want to support an open ended set of new abstractions you need to go the uniform objects route. We're stuck with numbers, strings, and booleans and their corresponding wrappers. We don't need more special cases at that level.

I'd be interested in hearing how this makes any difference to you from an implementation perspective. Even when symbols are specified as exotic objects you can still encode them as immediate values, just like you would a SmallInteger in Smalltalk. It's only when actual object MOP operations are applied to them that you should have to do any special casing but these are generally the same situations where you would have to auto-wrap primitive values.

The difference is that with real primitives + wrapper objects every respective operation has a single place where it does the ToObject conversion (which exists already), and downstream you can easily make it an invariant that what you've got is an ordinary object with an ordinary object representation. No special casing required.

If, on the other hand, you make new primitives pseudo-objects, but still want to represent them efficiently, then all parts that deal with objects now also have to deal with those denormal representations. You say "only actual MOP operations", but surely there are several times more occurrences of those than of ToObject. It involves the majority of language operations, and in contemporary implementations, every one of them potentially has a dozen or more possible implementations.

Sounds like your are concerned about special casing MOP dispatches on primitives representations. A good concern. However, I don't actually see much benefit of a primitive non-dispatchable representation for symbols and if you do, you could still hide it in ToObject. Nothing saying you can't have internal wrapper objects and be careful not to leak themselves to the language level.

Alternatively, you could introduce two different representations for these pseudo-objects and normalise their representation to a real object wherever you perform ToObject. That is, you essentially introduce the wrapper internally in the implementation. But then you have the dual problem: because this has to be transparent, now all contexts that expect a proper symbol also have to deal with wrapped symbols (and probably have to reverse the normalisation). And those are quite a few as well. Likewise, you have to special-case equality, maps, and similar generic operations.

I don't buy it. For symbols you don't inherently have to have two different representations. The use of MOP operations on symbols is going to be very rare. Any you have two have some sort generalized exotic object MOP dispatch mechanism. Because MOP operations on symbols are never important, so that representation should be fine from the object perspective. For actual property access, its only the identify of the symbol that matters.

I sense, that you issue may be that you have implementation issues relating to property looking/caching that perhaps relates to your representation of string primitive values. Is your issue that a symbol primitive typed modeled after strings will be easer to fit into your implementation? If so, it isn't clear that this ease of retrofit consideration carries over to other implementations.

Whichever path you take, artificially unifying two different concepts that have completely disjoint uses and requirements is not going to make anything simpler.

All this may be a worthwhile price to pay if it actually added any value to the language. But I don't see a notable benefit for programmers either. Am I missing something?

Yes, it's drawing a line and saying no added special case primitive types and no new wrapper objects. Starting with ES6 all new abstractions can be conceptualized as "objects".

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

We're stuck with numbers, strings, and booleans and their corresponding wrappers. We don't need more special cases at that level.

I should write an apologator (www-archive.mozilla.org/apology.html) for inflicting primitives as "non-objects" in those ten days in May. Agree they are an anti-pattern at this point, not to be imitated by symbol/Symbol (and I thought TC39 agreed on this, and no strawman or proposal had both).

For symbols you don't inherently have to have two different representations.

Not only do you, or we, not face a priori arguments for symbol and Symbol, users do not want.

Yes, it's drawing a line and saying no added special case primitive types and no new wrapper objects. Starting with ES6 all new abstractions can be conceptualized as "objects".

Agreed. Not to dogpile on, but just to apologize for primitives and say that they are the exception that proves the rule, not the pattern for novelties. See also int64, bignum, etc.

# Andreas Rossberg (11 years ago)

On 16 July 2013 19:33, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jul 16, 2013, at 4:34 AM, Andreas Rossberg wrote:

On 15 July 2013 19:44, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

User visible semantics comes down to whether or not there is a Symbol wrapper object. As far as I can tell, tell from the notes, there was no consensus WRT Symbol wrappers record in March and when I tried to convert to Symbols as primitive values in the spec. I don't provide such wrappers. Instead, I made ToObject throw for symbols values.

OK, that might explain the difficulty you faced. My understanding of the March agreement certainly was that there is a wrapper object, consistent with other primitive types. And I'm pretty sure that that makes the spec quite simple and regular.

Yes, wrappers would make it easier, but my take away from the meeting and subsequent discussion was that we didn't want to add any more observable wrappers.

Hm, that wasn't my take-away. In any case, it wasn't the intention of what I proposed. :)

We really should avoid adding new primitive types and wrapper objects. Value objects are the way to go, starting with Symbol.

I'd argue for the contrary, namely that we should avoid artificially cramping more inappropriate concepts into the notion of 'object'! These are not objects by any useful definition of the word, despite the MOP being rich enough to support them as degenerate cases.

Clearly I disagree. In ES and other polymorphic object-based languages, objects are really the basis for uniformity of references. Any abstraction can be represented as an object and any operation can be manifested as method invocations upon an object.

It is the ES primitive types that introduce non-uniformity that either needs to be special cased or partially hidden behind hacks such as automatic conversion to wrapper objects.

For better or worse, ES is not a puristic everything-is-an-object language. I understand your desire to increase coherence, but in this case, you are actually decreasing it: in almost all respects, symbols act like strings, and they are used in the same contexts as strings. So coherence strongly argues that they should behave like strings. I think it's causing unnecessary friction to diverge the two.

Clearly at an implementation level you want to have optimized representations of certain kinds of entities. You can get there two different ways. You can expose the optimized representation as "primitive types" that requires user level special casing or you can uniformly expose everything as an "object" and let the implementation opaquely use special case representations where it suits it.

It seems clear to me, that if you want to support an open ended set of new abstractions you need to go the uniform objects route. We're stuck with numbers, strings, and booleans and their corresponding wrappers. We don't need more special cases at that level.

Non-object types are only "special cases" with your everything-is-an-object hat on. ;)

The hack in ES is not that it has non-object types, it's that it tries to fake everything-is-an-object with wrappers. I'd prefer that wrapping didn't exist. But it does, and we're not getting rid of it. Given that, I don't think that introducing a whole new class of pseudo-object types in addition is going to help anybody.

Sounds like your are concerned about special casing MOP dispatches on primitives representations. A good concern. However, I don't actually see much benefit of a primitive non-dispatchable representation for symbols and if you do, you could still hide it in ToObject. Nothing saying you can't have internal wrapper objects and be careful not to leak themselves to the language level.

Yes, that was the alternative I mention below.

Alternatively, you could introduce two different representations for these pseudo-objects and normalise their representation to a real object wherever you perform ToObject. That is, you essentially introduce the wrapper internally in the implementation. But then you have the dual problem: because this has to be transparent, now all contexts that expect a proper symbol also have to deal with wrapped symbols (and probably have to reverse the normalisation). And those are quite a few as well. Likewise, you have to special-case equality, maps, and similar generic operations.

I don't buy it. For symbols you don't inherently have to have two different representations. The use of MOP operations on symbols is going to be very rare.

How does it matter that it's rare? Rare corner cases that nobody really cares about but that still need to be handled correctly is what's causing a substantial part of the complexity of JavaScript VMs. Please let's try to limit adding more without good reason.

Any you have two have some sort generalized exotic object MOP dispatch mechanism.

True, but the complexity of this mechanism grows about linearly with the number of kinds of exotic objects that we add, because they will all introduce new cases. So you want to keep the number low.

Don't get too hung up on the MOP. In general, the MOP is not a useful level of abstraction for implementations. It's merely a spec artifact. Something that looks simple in terms of the MOP may still have a complex and irregular implementation (or semantics, for that matter).

Because MOP operations on symbols are never important, so that representation should be fine from the object perspective. For actual property access, its only the identify of the symbol that matters.

That's not true for implementations. For example, you need hash values for symbols.

I sense, that you issue may be that you have implementation issues relating to property looking/caching that perhaps relates to your representation of string primitive values. Is your issue that a symbol primitive typed modeled after strings will be easer to fit into your implementation? If so, it isn't clear that this ease of retrofit consideration carries over to other implementations.

That's certainly true as well, e.g. I want to access hashes uniformly (and I believe that other implementations will want to follow a similar strategy to avoid the cost of extra case distinctions). But it's not the only point, see above.

Whichever path you take, artificially unifying two different concepts that have completely disjoint uses and requirements is not going to make anything simpler.

All this may be a worthwhile price to pay if it actually added any value to the language. But I don't see a notable benefit for programmers either. Am I missing something?

Yes, it's drawing a line and saying no added special case primitive types and no new wrapper objects. Starting with ES6 all new abstractions can be conceptualized as "objects".

As said above, we already have the primitive case, and you are just adding yet another kind of beast. I don't think that would be improving anything. On the contrary. MOP-level niceness notwithstanding.

# Brendan Eich (11 years ago)

Andreas Rossberg wrote:

As said above, we already have the primitive case, and you are just adding yet another kind of beast. I don't think that would be improving anything. On the contrary.

This is an argument from minimization of primitive concepts or kinds, but I argue the better way on the web (given backward compatibility) is not to mimimize at such a reductive level. Users mostly ignore the boolean, number, and string wrappers, which are unobservable in strict mode. Users do not want more wrappers, e.g., Uint64 for uint64. No use-case is served by such beasts.

You might argue that "total cognitive load" is lower, but I reply that since wrappers are almost completely unobservable and not used explicitly, the load of having two types, symbol/Symbol, bignum/Bignum, etc., is strictly higher.

# Mark S. Miller (11 years ago)

On Wed, Jul 17, 2013 at 8:55 AM, Brendan Eich <brendan at mozilla.com> wrote:

Andreas Rossberg wrote:

As said above, we already have the primitive case, and you are just adding yet another kind of beast. I don't think that would be improving anything. On the contrary.

This is an argument from minimization of primitive concepts or kinds, but I argue the better way on the web (given backward compatibility) is not to mimimize at such a reductive level. Users mostly ignore the boolean, number, and string wrappers, which are unobservable in strict mode.

Nit: They are not unobservable. Rather, strict code itself never implicitly wraps. However, for example,

({}).valueOf.call(3) // 3 wrapper
# Brendan Eich (11 years ago)

Mark S. Miller wrote:

Nit: They are not unobservable. Rather, strict code itself never implicitly wraps.

Thanks, I meant that, but my point is users really don't run into boolean/Boolean, number/Number, and string/String complexity in the main. Yes, one must use String.prototype (not string.prototype, there's no 'string' of course), but such minor irregularities in languages are a lesser issue.

However, for example,

({}).valueOf.call(3) // 3 wrapper

Right.

Users do not want more wrappers, e.g., Uint64 for uint64. No use-case is served by such beasts.

You might argue that "total cognitive load" is lower, but I reply that since wrappers are almost completely unobservable and not used explicitly, the load of having two types, symbol/Symbol, bignum/Bignum, etc., is strictly higher.

I'm hoping you agree here!

# Allen Wirfs-Brock (11 years ago)

On Jul 17, 2013, at 7:46 AM, Andreas Rossberg wrote:

On 16 July 2013 19:33, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: ...

Because MOP operations on symbols are never important, so that representation should be fine from the object perspective. For actual property access, its only the identify of the symbol that matters.

That's not true for implementations. For example, you need hash values for symbols.

I sense, that you issue may be that you have implementation issues relating to property looking/caching that perhaps relates to your representation of string primitive values. Is your issue that a symbol primitive typed modeled after strings will be easer to fit into your implementation? If so, it isn't clear that this ease of retrofit consideration carries over to other implementations.

That's certainly true as well, e.g. I want to access hashes uniformly (and I believe that other implementations will want to follow a similar strategy to avoid the cost of extra case distinctions). But it's not the only point, see above.

You also need hash values for objects to implement Maps/Sets, etc.

Representationally, I would expect most implementations to have a common base representation for all heap allocated entities including strings and objects. But your design may be different.

Regardless, I think it is quite possible to exhibit property lookup designs where object-baeed keys make absolutely no complexity/performance difference. (its a different language, but high perf Smalltalk engines come to mind). So it is probably a wash. Some existing implementations may have to do more and some less to support symbols as objects. That's generally the case for most new engine-level ES features.

# Mark S. Miller (11 years ago)

On Wed, Jul 17, 2013 at 9:11 AM, Brendan Eich <brendan at mozilla.com> wrote:

Users do not want more wrappers, e.g., Uint64 for uint64. No use-case is served by such beasts.

You might argue that "total cognitive load" is lower, but I reply that since wrappers are almost completely unobservable and not used explicitly, the load of having two types, symbol/Symbol, bignum/Bignum, etc., is strictly higher.

I'm hoping you agree here!

I do. But the direction we're going does have a complexity cost. All new "types" of things, whether symbols or bignums or whatever, should be new object types, where we introduce a further distinction between "value" objects (need a better word) which are immutable and compare based on contents (bignums) vs unique objects which compare based on unforgeable EQness identity. Curiously, symbols are unforgeable, and so are in the latter camp.

I am uncomfortable expanding the typeof namespace. Where do these new names come from, and how do we avoid collision?

# Andreas Rossberg (11 years ago)

On 17 July 2013 18:13, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jul 17, 2013, at 7:46 AM, Andreas Rossberg wrote:

On 16 July 2013 19:33, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Because MOP operations on symbols are never important, so that representation should be fine from the object perspective. For actual property access, its only the identify of the symbol that matters.

That's not true for implementations. For example, you need hash values for symbols.

I sense, that you issue may be that you have implementation issues relating to property looking/caching that perhaps relates to your representation of string primitive values. Is your issue that a symbol primitive typed modeled after strings will be easer to fit into your implementation? If so, it isn't clear that this ease of retrofit consideration carries over to other implementations.

That's certainly true as well, e.g. I want to access hashes uniformly (and I believe that other implementations will want to follow a similar strategy to avoid the cost of extra case distinctions). But it's not the only point, see above.

You also need hash values for objects to implement Maps/Sets, etc.

Yes, but they are rarely needed, so you don't want to waste space on every object. For symbols, however, the trade-off is different.

Representationally, I would expect most implementations to have a common base representation for all heap allocated entities including strings and objects. But your design may be different.

The only commonality is the presence of a header word. Beyond that, JavaScript objects are very heavyweight. Heap-allocated primitives or symbols are not. There isn't much to share.

Regardless, I think it is quite possible to exhibit property lookup designs where object-baeed keys make absolutely no complexity/performance difference. (its a different language, but high perf Smalltalk engines come to mind). So it is probably a wash. Some existing implementations may have to do more and some less to support symbols as objects. That's generally the case for most new engine-level ES features.

I doubt that there is a single implementation that would not want to special-case symbol representations. They will all have to deal with the extra complexity.

# Allen Wirfs-Brock (11 years ago)

On Jul 17, 2013, at 9:20 AM, Mark S. Miller wrote:

I am uncomfortable expanding the typeof namespace. Where do these new names come from, and how do we avoid collision?

The typeof namespace has always been extensible and has historically been extended by some implementations. But the only possible source of collusions are such implementations not the whole web. Note that providing a typeof value is one thing that we have not exposed via the MOP and in particular a Proxies. There is current no explicit extensibility mechanism for them.

However, I'm not sure we would want to add a new typeof result for every possible "value object" type. That probably would require an extension mechanism.

Note that instanceof can be made into a reliable test for value objects.

I will defined Symbols.@@hasInstance such that obj instanceof Symbol is a reliable, cross-Realm test of obj's symbolness.

# Andreas Rossberg (11 years ago)

On 17 July 2013 17:55, Brendan Eich <brendan at mozilla.com> wrote:

Andreas Rossberg wrote:

As said above, we already have the primitive case, and you are just adding yet another kind of beast. I don't think that would be improving anything. On the contrary.

This is an argument from minimization of primitive concepts or kinds, but I argue the better way on the web (given backward compatibility) is not to mimimize at such a reductive level. Users mostly ignore the boolean, number, and string wrappers, which are unobservable in strict mode. Users do not want more wrappers, e.g., Uint64 for uint64. No use-case is served by such beasts.

If users ignore them anyway, why would they care? Your observation seems to imply that users don't care about primitives being able to facade as objects. And that in turn implies that there is no need to jump through hoops to support yet another mechanism for object facading.

There are tons of corner cases that you have to worry about for anything that is to act as an object. They can be prototypes, they can be proxied, they can be reflected, etc. None of this is of any practical use for symbols. It only causes wrong expectations (for example, to be fully coherent, you would have to allow using a symbol proxy as a symbol).

# Brendan Eich (11 years ago)

Mark S. Miller wrote:

I am uncomfortable expanding the typeof namespace. Where do these new names come from, and how do we avoid collision?

You still value the

(x == y && typeof x == y) <=> x === y

invariant, right? That's the motivation for decimal, int64, etc. having their constructor-named "decimal", "int64", etc., typeof-result strings.

We decide these for ES7 for sure, since we're considering value objects for important machine and user-wanted types (int64, bignum).

We also agreed, in deferring decimal (late for ES5 anyway), that it should happen via self-hosted "user code". You and I discussed up-thread, and I'm going to write a strawman for operators and literals.

Suffice to say here that such user-defined value objects should enable typeof-result customization. There could be conflicts, just as literals' suffixes could conflict. This is not a deal-breaker, and I should work on that strawman before we over-rotate on it. But can we agree here that we're trying not to hardcode value objects in future editions, rather cover the important machine and user-facing types and support self-hosting, and let library authors extend the system rather than TC39?

Note also that IE JScript has non-standard typeof-results, and this gave us hope (when we last discussed it) that we too could extend typeof's codomain.

# Domenic Denicola (11 years ago)

From: Brendan Eich [brendan at mozilla.com]

You still value the

(x == y && typeof x == y) <=> x === y

invariant, right? That's the motivation for decimal, int64, etc. having their constructor-named "decimal", "int64", etc., typeof-result strings.

You have brought up this invariant in many threads. Could you explain its importance and value? My feeling is that nobody uses ==, so a relation tying together ==, typeof, and === doesn't really impact anything people use.

Put another way: why do you feel this is an important base invariant of the language, to be preserved, rather than just an equality that happens to fall out of what we have in the language now?

# Brendan Eich (11 years ago)

Andreas Rossberg wrote:

I doubt that there is a single implementation that would not want to special-case symbol representations. They will all have to deal with the extra complexity.

Don't take this as a "you're wrong"-style absolute argument:

We took this hit way back when, for E4X (to help restart Ecma TC39). RIP, and put a stake through it and sew salt into its mouth so it does not come back to life, but my point here is that engines already special-case integer-ish index identifiers and do not use strings (while equating strings such as "123" to 123 when used as property names).

True, adding another variant for symbol property names where the symbol is an object, not a unique and unforgeable string or similar primitive, is a cost. But it's the 3rd tag in the space, not the 2nd. So there already exist special cases for int-ish names. The cost is less than you suggest, in terms of PIC variants and so on.

Finally, while implementor hardships matter, we must subordinate symbol implementation concerns to user-facing ones. But that's an exchange where I already made my pitch elsewhere on this thread.

# Brendan Eich (11 years ago)

Domenic Denicola wrote:

From: Brendan Eich [brendan at mozilla.com]

You still value the

(x == y&&  typeof x == y)<=>  x === y

invariant, right? That's the motivation for decimal, int64, etc. having their constructor-named "decimal", "int64", etc., typeof-result strings.

You have brought up this invariant in many threads.

Yes, and I've written about why invariants matter on occasion -- perhaps you missed that :-P.

esdiscuss/2008-September/007610


Postel's Law means you accept everything that flies in ES1, and have trouble being less liberal in ES2+. Web authors crawl the feature vector space and find all the edges, so at least what you did not accept in v1 becomes law. But these are generalizations from experience with invariants such as typeof x == "object"&& !x => x === null and typeof x == typeof y => (x == y<=> x === y).

Beyond this conservatism in breaking invariants based on experience, it turns out that % and / results do flow into array indexes. From SunSpider's 3d-raytrace.js....


Could you explain its importance and value? My feeling is that nobody uses ==,

Your feeling is wrong. == is used (I miss Google codesearch) widely on the web.

so a relation tying together ==, typeof, and === doesn't really impact anything people use.

See above. Language designers value and conserve invariants based on long experience with painful counterexamples.

Put another way: why do you feel this is an important base invariant of the language, to be preserved, rather than just an equality that happens to fall out of what we have in the language now?

It's not an equality, rather a two-way implication. It helps us reason about all three of typeof, ==, and === as we evolve JS. It's not noise, and not nothing. We could deliberately break it, but for what win?

# Brendan Eich (11 years ago)

Andreas Rossberg wrote:

On 17 July 2013 17:55, Brendan Eich<brendan at mozilla.com> wrote:

Andreas Rossberg wrote:

As said above, we already have the primitive case, and you are just adding yet another kind of beast. I don't think that would be improving anything. On the contrary. This is an argument from minimization of primitive concepts or kinds, but I argue the better way on the web (given backward compatibility) is not to mimimize at such a reductive level. Users mostly ignore the boolean, number, and string wrappers, which are unobservable in strict mode. Users do not want more wrappers, e.g., Uint64 for uint64. No use-case is served by such beasts.

If users ignore them anyway, why would they care?

I wrote "mostly"intentionally. Where they don't or can't ignore primitives and their wrappers, they generally feel pain.

We should not add more such user-facing pain if we can avoid it -- even if that means more implementor-facing pain (remember Mr. Spock's Kobayashi Maru solution ;-).

# Domenic Denicola (11 years ago)

From: Brendan Eich [brendan at mozilla.com]

Yes, and I've written about why invariants matter on occasion -- perhaps you missed that :-P.

Heh, 2008 was before my time. But generally yes, I certainly understand the importance of invariants. I am just not sure this particular invariant is something people actually depend on.

This I guess is the heart of my question. When I said "nobody uses ==," I should have said "nobody writes code using == that depends on that logical equivalence." The typeof x == "object" && !x => x === null invariant I can certainly see being used in the wild, mainly to avoid the special-caseness of null being an object-according-to-typeof, but I can't for the life of me think of how to use the typeof/==/=== relation.

It's not an equality, rather a two-way implication. It helps us reason about all three of typeof, ==, and === as we evolve JS. It's not noise, and not nothing.

My perspective is that reasoning about ==, and even to some extent typeof, is not that important, since they are both "broken." And thus such a connection does feel somewhat like noise; there are plenty of invariants you can state by just stringing together logical consequences of the language, but determining which of those are important is the trick.

Anyway, I get it that good invariants are useful and that you at least find this particular invariant to be a good one, so we can leave it at that. I'd still love to see some plausible-in-the-wild code that could break if we lost this invariant, though.

# Brendan Eich (11 years ago)

Domenic Denicola wrote:

From: Brendan Eich [brendan at mozilla.com]

Yes, and I've written about why invariants matter on occasion -- perhaps you missed that :-P.

Heh, 2008 was before my time. But generally yes, I certainly understand the importance of invariants. I am just not sure this particular invariant is something people actually depend on.

People do depend on this, though. Some JS users take advantage of ==, in particular of

  • null == undefined
  • typeof x == typeof y

The latter makes use of knowledge that typeof's result is always of string type.

You may argue people should always use === everywhere, but it's a fact that == is supported and used.

This I guess is the heart of my question. When I said "nobody uses ==," I should have said "nobody writes code using == that depends on that logical equivalence."

False. When operand typeof-types match, == is short for === and people use this (second bullet above is the most common case I've seen, but not the only case).

The typeof x == "object"&& !x => x === null invariant I can certainly see being used in the wild, mainly to avoid the special-caseness of null being an object-according-to-typeof, but I can't for the life of me think of how to use the typeof/==/=== relation.

As I wrote last time, we language designers/stewards of TC39 care for good reason.

It's not an equality, rather a two-way implication. It helps us reason about all three of typeof, ==, and === as we evolve JS. It's not noise, and not nothing.

My perspective is that reasoning about ==, and even to some extent typeof, is not that important, since they are both "broken."

Let's cut this thread mercifully short. Language designers tending ECMA-262 cannot turn such a blind eye, period, full stop.

# Brendan Eich (11 years ago)

Brendan Eich wrote:

My perspective is that reasoning about ==, and even to some extent typeof, is not that important, since they are both "broken."

Let's cut this thread mercifully short. Language designers tending ECMA-262 cannot turn such a blind eye, period, full stop.

It's fine if users subset. This relates to my point about users "mostly" ignoring primitives vs. wrappers.

However, you have the burden of proof backwards on TC39 chucking invariants. Dave Herman has written about this before and reminded me to make this point crystal clear. Show why we should ditch the invariant. Otherwise we keep it.

And practically speaking, with typeof-type extensions on the table, and typeof-result being a string used to enable == not ===, we need this one.

# Claude Pache (11 years ago)

Le 17 juil. 2013 à 18:43, Andreas Rossberg <rossberg at google.com> a écrit :

If users ignore them anyway, why would they care?

If symbols are primitives with wrapper, they must care: They must know that new Symbol does not produce a new symbol, but some useless object. At least, new Symbol should either (as I have already said) be poisoned or (better) produce a new symbol.

# Brandon Benvie (11 years ago)

On 7/17/2013 3:58 PM, Claude Pache wrote:

Le 17 juil. 2013 à 18:43, Andreas Rossberg <rossberg at google.com> a écrit :

If users ignore them anyway, why would they care? If symbols are primitives with wrapper, they must care: They must know that new Symbol does not produce a new symbol, but some useless object.

And this is how it currently works in the V8 implementation. The first thing I did testing it looked like:

 var s = new Symbol();
 var x = {};
 x[s] = 'test';

I was surprised to find that this threw an error instead of doing the (to me) obvious thing.

# Brandon Benvie (11 years ago)

On 7/17/2013 4:02 PM, Brandon Benvie wrote:

And this is how it currently works in the V8 implementation. The first thing I did testing it looked like:

var s = new Symbol();
var x = {};
x[s] = 'test';

I was surprised to find that this threw an error instead of doing the (to me) obvious thing.

(And to clarify, it throws on line 3, not line 1)

# Brendan Eich (11 years ago)

Brandon Benvie wrote:

On 7/17/2013 4:02 PM, Brandon Benvie wrote:

And this is how it currently works in the V8 implementation. The first thing I did testing it looked like:

var s = new Symbol();
var x = {};
x[s] = 'test';

I was surprised to find that this threw an error instead of doing the (to me) obvious thing.

(And to clarify, it throws on line 3, not line 1)

This is nuts.

# Andreas Rossberg (11 years ago)

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

We should not add more such user-facing pain if we can avoid it -- even if that means more implementor-facing pain (remember Mr. Spock's Kobayashi Maru solution ;-).

An inconsistency between strings and symbols is both a user-facing and an implementor-facing pain.

# Andreas Rossberg (11 years ago)

On 18 July 2013 01:09, Brendan Eich <brendan at mozilla.com> wrote:

Brandon Benvie wrote:

On 7/17/2013 4:02 PM, Brandon Benvie wrote:

And this is how it currently works in the V8 implementation. The first thing I did testing it looked like:

var s = new Symbol();
var x = {};
x[s] = 'test';

I was surprised to find that this threw an error instead of doing the (to me) obvious thing.

(And to clarify, it throws on line 3, not line 1)

This is nuts.

May I humbly remind you that we explicitly discussed and decided this at the March meeting? I actually would have preferred if 'new Symbol' worked, and that was what V8 implemented before the meeting.

Seriously, all this discussion is about breaking the March consensus. I find it rather irritating that some seem surprised about decisions they actively had their part in. Moreover, I find it unacceptable when the editor decides to break consensus single-handedly and implements it without notice, let alone discussion.

I'm fine with revisiting aspects of the decision, such as the 'new' issue. But the basic symbols as primitives decision, not so much. Nothing new has come up here that would change the grounds for that decision.

# Claude Pache (11 years ago)

IIUC, the problem with symbols-as-primitives is that, on one hand, wrapper objects are not wanted, but, on the other hand, getting rid of wrappers leads to complications.

My suggestion is to allow wrapper objects to exist in the spec, but to completely hide them from the user by doing an implicit unwrapping whenever applicable. Thus, for any primitive class P (such as Symbol, Bignum), except for legacy ones (Number, Boolean and String).

  • new P produces an unwrapped value;
  • p => Object(p) becomes a no-op;
  • Object.defineProperty, Object.getPrototypeOf`, etc., return an unwrapped value when applicable;
  • plus some further details I've forgotten.

(Technically, I think naively that it suffices to define an UnwrapIfNonLegacyPrimitive() abstract operation and to apply it in correct places.)

Moreover, in order to make primitives look more like objects, some adjustments could be done:

  • instanceof could wrap its LHS if it is a primitive value instead of throwing an error, so that tests like s instanceof Symbol would work;
  • Wrappers should be frozen at creation, so that things like s.foo = 17 or Object.setPrototypeOf(s, bar) would fail noisily (at least in strict mode);
  • etc.
# Andreas Rossberg (11 years ago)

On 18 July 2013 14:22, Claude Pache <claude.pache at gmail.com> wrote:

IIUC, the problem with symbols-as-primitives is that, on one hand, wrapper objects are not wanted, but, on the other hand, getting rid of wrappers leads to complications.

My suggestion is to allow wrapper objects to exist in the spec, but to completely hide them from the user by doing an implicit unwrapping whenever applicable. Thus, for any primitive class P (such as Symbol, Bignum), except for legacy ones (Number, Boolean and String).

  • new P produces an unwrapped value;
  • p => Object(p) becomes a no-op;
  • Object.defineProperty, Object.getPrototypeOf`, etc., return an unwrapped value when applicable;
  • plus some further details I've forgotten.

I'm not sure I understand what this would achieve. AFAICS, it is observably equivalent to making Symbol an object, no? So it would have the exact same implications.

# Brendan Eich (11 years ago)

Andreas Rossberg wrote:

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

We should not add more such user-facing pain if we can avoid it -- even if that means more implementor-facing pain (remember Mr. Spock's Kobayashi Maru solution ;-).

An inconsistency between strings and symbols is both a user-facing and an implementor-facing pain.

Strings and symbols are quite different according to typeof and behavior, in any proposal or implementation. How does providing a wrapper which when used throws -- unlike ({}[new String('x')]) which does not throw -- help make symbol like string in any user-facing sense?

# Brendan Eich (11 years ago)

Andreas Rossberg wrote:

On 18 July 2013 01:09, Brendan Eich<brendan at mozilla.com> wrote:

Brandon Benvie wrote:

On 7/17/2013 4:02 PM, Brandon Benvie wrote:

And this is how it currently works in the V8 implementation. The first thing I did testing it looked like:

 var s = new Symbol();
 var x = {};
 x[s] = 'test';

I was surprised to find that this threw an error instead of doing the (to me) obvious thing. (And to clarify, it throws on line 3, not line 1) This is nuts.

May I humbly remind you that we explicitly discussed and decided this at the March meeting?

If it's nuts now, it was nuts in March :-P.

I actually would have preferred if 'new Symbol' worked, and that was what V8 implemented before the meeting.

You are right, I see this in the March 14 meeting notes. So I'm pleading temporary insanity. We'll have to reestablish consensus. I will refrain from more (self-)analysis ;-).

# Brendan Eich (11 years ago)

Brendan Eich wrote:

I actually would have preferred if 'new Symbol' worked, and that was what V8 implemented before the meeting.

And just to be clear, what you implemented before the meeting, symbol primitive with Symbol auto-wrapper and no throw on ({}[new Symbol]) seems better.

I think the issue some of us are having is auto-wrapping rather than taking the value objects approach. If we are doing value objects as discussed, then for symbols we have a choice: follow the string pattern, or follow the int64, bignum, etc. proposal.

Of course value objects are not in ES6, and you may disagree on that strawman's eschewing of auto-wrappers (I can't tell). Both of these objections, whatever the ultimate disposition of value objects, seem to me to be legitimate cause to favor the string pattern in ES6. But we could be suffering from path dependence in taking this shorter-term-conservative approach.

A separate issue with the string pattern: we do not want symbol objects converting by toString, which motivated throwing instead. Claude just suggested auto-unwrapping when used as a property name. I'll follow up in reply to that sub-thread.

# Brendan Eich (11 years ago)

Andreas Rossberg wrote:

On 18 July 2013 14:22, Claude Pache<claude.pache at gmail.com> wrote:

IIUC, the problem with symbols-as-primitives is that, on one hand, wrapper objects are not wanted, but, on the other hand, getting rid of wrappers leads to complications.

My suggestion is to allow wrapper objects to exist in the spec, but to completely hide them from the user by doing an implicit unwrapping whenever applicable. Thus, for any primitive class P (such as Symbol, Bignum), except for legacy ones (Number, Boolean and String).

  • new P produces an unwrapped value;
  • p => Object(p) becomes a no-op;
  • Object.defineProperty, Object.getPrototypeOf`, etc., return an unwrapped value when applicable;
  • plus some further details I've forgotten.

I'm not sure I understand what this would achieve. AFAICS, it is observably equivalent to making Symbol an object, no? So it would have the exact same implications.

What implications do you object to, though. We have trouble making symbols like unforgeable strings. They must have distinct typeof

# Allen Wirfs-Brock (11 years ago)

On Jul 18, 2013, at 5:22 AM, Claude Pache wrote:

IIUC, the problem with symbols-as-primitives is that, on one hand, wrapper objects are not wanted, but, on the other hand, getting rid of wrappers leads to complications.

My suggestion is to allow wrapper objects to exist in the spec, but to completely hide them from the user by doing an implicit unwrapping whenever applicable. Thus, for any primitive class P (such as Symbol, Bignum), except for legacy ones (Number, Boolean and String).

  • new P produces an unwrapped value;
  • p => Object(p) becomes a no-op;
  • Object.defineProperty, Object.getPrototypeOf`, etc., return an unwrapped value when applicable;
  • plus some further details I've forgotten.

(Technically, I think naively that it suffices to define an UnwrapIfNonLegacyPrimitive() abstract operation and to apply it in correct places.)

Moreover, in order to make primitives look more like objects, some adjustments could be done:

  • instanceof could wrap its LHS if it is a primitive value instead of throwing an error, so that tests like s instanceof Symbol would work;
  • Wrappers should be frozen at creation, so that things like s.foo = 17 or Object.setPrototypeOf(s, bar) would fail noisily (at least in strict mode);
  • etc.

This is very similar to the the point I arrived at in trying to edit wrapper-less primitive symbols into the spec. However, I quickly realized that all these special cases are the equivalent of simply specifying symbols as exotic objects.

# Allen Wirfs-Brock (11 years ago)

On Jul 18, 2013, at 8:09 AM, Brendan Eich wrote:

Andreas Rossberg wrote:

On 18 July 2013 01:09, Brendan Eich<brendan at mozilla.com> wrote:

Brandon Benvie wrote:

On 7/17/2013 4:02 PM, Brandon Benvie wrote:

And this is how it currently works in the V8 implementation. The first thing I did testing it looked like:

var s = new Symbol();
var x = {};
x[s] = 'test';

I was surprised to find that this threw an error instead of doing the (to me) obvious thing. (And to clarify, it throws on line 3, not line 1) This is nuts.

May I humbly remind you that we explicitly discussed and decided this at the March meeting?

If it's nuts now, it was nuts in March :-P.

I actually would have preferred if 'new Symbol' worked, and that was what V8 implemented before the meeting.

You are right, I see this in the March 14 meeting notes. So I'm pleading temporary insanity. We'll have to reestablish consensus. I will refrain from more (self-)analysis ;-).

No, you just need to read carefully, I remember that this is just a summary of the discussion, not literal quotes, and not everything is captured:

AWB: Symbol is a factory that creates symbols, new Symbol creates instance of the wrapper class. (same as Number)

BE: Value objects allow "new"

AWB: I can define the Symbol[@@create] to throw

I'm pretty sure that as part of the first statement I also said that 'new Symbol' should not create a primitive value because that would violate the wrapper object pattern. The Synbol[@@create]] comment is another way of saying 'new Symbol' should throw.

There is nothing following this in the Symbols section of the minutes that address whether or not there are Symbol wrappers. My take-away from the meeting was we would try to do Symbols as as a primitive, with a factory object named 'Symbol', but no user visible wrapper instances. I don't see anything in the minutes that says otherwise. In fact, the conclusion/resolution doesn't even say that Symbols will be primitive values. All of the bullet items listed there are apply equally to either symbols as primitives or symbols as exotic objects. The current (and previous, I believe) spec. draft reflects those explicit conclusions.

# Allen Wirfs-Brock (11 years ago)

On Jul 18, 2013, at 12:56 AM, Andreas Rossberg wrote:

On 18 July 2013 01:09, Brendan Eich <brendan at mozilla.com> wrote:

Brandon Benvie wrote:

On 7/17/2013 4:02 PM, Brandon Benvie wrote:

And this is how it currently works in the V8 implementation. The first thing I did testing it looked like:

var s = new Symbol(); var x = {}; x[s] = 'test';

I was surprised to find that this threw an error instead of doing the (to me) obvious thing.

(And to clarify, it throws on line 3, not line 1)

This is nuts.

May I humbly remind you that we explicitly discussed and decided this at the March meeting? I actually would have preferred if 'new Symbol' worked, and that was what V8 implemented before the meeting.

from the minutes: AWB: I can define the Symbol[@@create] to throw

that means 'new Symbol' would throw.

But, as I mentioned in another message, I actually don't see in the minutes a record of consensus one way or another regarding Symbol wrapper objects. I do see push back against them and by I personally went away believing that we agreed to try primitive symbols with no wrapper objects. If you look at the Rev15 (May 2013) spec. draft the timestamp on the edits to introduce those changes is March 18, 2013. So, I made those changes 4 days after the meeting discussion and presumably with a fresh recollection of what transpired.

Seriously, all this discussion is about breaking the March consensus. I find it rather irritating that some seem surprised about decisions they actively had their part in. Moreover, I find it unacceptable when the editor decides to break consensus single-handedly and implements it without notice, let alone discussion.

Unfortunately, we don't even agree upon the details of the consensus. These consensus agreements at meetings are important, and I always try to get them into the spec. draft as soon as possible. However, they are typically made in the context of a relatively short "debate" where it is impossible for everybody to explore and understand all the deep implications of a decision. Sometimes we reach a consensus that latter proves to be problematic.

I have to take the result of those decisions and try to make them work in the spec. and implementors should be trying to make them work in their engines. I immediately incorporated the my understanding of the consensus into what was ultimately released as the May draft. After that I started to routinely discover numerous additional special cases in the spec. where primitive symbols had to be explicitly accounted for and also discovered that it was something that I was having to consider (or was forgetting to consider) as I added new material to the spec. My conclusion, was that primitive symbols without wrappers wasn't going to work.

I know I informally talked about this with several people. I reverted symbol to being an exotic object May 29 shortly after getting back from the May TC39 meeting so something that came out of that meeting pushed me over the edge towards reverting. I think it probably was things I ran into while changing function call to use [[Invoke]].

At a meta level, I need to keep moving forward on the spec. or it will never be completed. We seldom are able to definitively resolve issues on es-discuss and I can't let myself get blocked for up to two months waiting for a TC39 meeting. So I have to make make informed guesses about ultimate outcomes and use them in my working drafts so I can move forward with a reasonably self-consistent spec. Every thing in the draft spec is subject to (and needs) serious review by TC39 members. As I say quite often, nothing in the spec. if final until the entire thing is approved by TC39. (BTW, I really appreciate the stream of good feedback continue to get from the V8 team via bugs.ecmascript.org).

In this particular case, I guessed that we were going to ultimately either go back to Symbols as objects or to introduce wrapper objects for primitive Symbols. So, I had basically three choices: 1) keep going with special case handling of primitive symbols scattered through out the spec. 2) Revert to a centralized definition of exotic symbol objects, 3) add symbol wrappers.

option 1 was the maintenance nightmare I was trying to recover from. I believed there was strong opposition to adding additional wrapper objects, so I didn't want to push a Symbol wrapper into the spec. yet. But it turns out that option 2, was also a reasonable place for moving to option 3 if we decided to go that route (both option 2&3 eliminates most of the special case handling). So, that's where we stand today. I'm betting that TC39 will ultimately decide on either symbols as exotic objects or primitive symbols with wrappers. The current state of the spec. makes it easy for me to go either direction.

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

No, you just need to read carefully,

Do you mean me, or Andreas? Or Rick who took the meeting notes?

The notes should not require reading between the lines, including your unrecorded statements! Not that notes are ever perfect.

Ok, everyone take a deep breath....

I remember that this is just a summary of the discussion, not literal quotes, and not everything is captured:

AWB: |Symbol| is a factory that creates symbols, |new Symbol|
creates instance of the wrapper class. (same as Number)

BE: Value objects allow "new"

AWB: I can define the |Symbol[@@create]| to throw

I'm pretty sure that as part of the first statement I also said that 'new Symbol' should not create a primitive value because that would violate the wrapper object pattern. The Synbol[@@create]] comment is another way of saying 'new Symbol' should throw.

Yes, and (whether it was March or May -- March I think) we did talk about value object constructors not supporting 'new'.

There is nothing following this in the Symbols section of the minutes that address whether or not there are Symbol wrappers. My take-away from the meeting was we would try to do Symbols as as a primitive, with a factory object named 'Symbol', but no user visible wrapper instances. I don't see anything in the minutes that says otherwise. In fact, the conclusion/resolution doesn't even say that Symbols will be primitive values. All of the bullet items listed there are apply equally to either symbols as primitives or symbols as exotic objects. The current (and previous, I believe) spec. draft reflects those explicit conclusions.

Ok, but we clearly had two people (at least) at the meeting who came to quite different conclusions. That's a problem. These things happen, we'll sort it out, but I'm not sure what you are doing here other than reiterating what you got from the notes. The notes are incomplete, in a way that supports multiple conflicting interpretations.

# Andreas Rossberg (11 years ago)

On 18 July 2013 17:53, Brendan Eich <brendan at mozilla.com> wrote:

What implications do you object to, though. We have trouble making symbols like unforgeable strings. They must have distinct typeof-type -- a symbol can't be "string" according to typeof.

Right, and nobody is suggesting that, AFAICT.

We must avoid any wrapper converting to a string when used as a property name, per your

ARB: The current spec has a toString for implicit conversion, which makes it too easy to convert the symbol to a string accidentally without realizing.

from the March meeting notes. This leaves only three choices AFAICT:

  1. Primitive symbol with Symbol auto-wrapper that throws when used as property name, the March consensus.

Fine with that. :)

  1. (1) but with Claude's suggestion: auto-unwrap on use of Symbol as property name.

Not sure why bother. As you pointed out yourself, wrappers rarely ever escape (otherwise we'd see far more complaints about if(Object(false)) and the like), so I don't think it matters much in practice.

But it wouldn't be too bad either. There would be a ToName conversion that distinguishes wrapped symbols from other objects (perhaps based on a new hint for ToPrimitive). This still has the advantage (over making them exotic objects) that there is no auto-wrapping other than the places where the spec already does ToObject, symmetrically with other primitives. In particular, no special casing for equality, keying, reflection, typeof, etc.

So I suppose I could live with that option.

  1. Value object approach: no Symbol wrapper, typeof says "symbol", spec treats symbol as exotic object per latest draft.

The implementation cost of every new exotic object is fairly substantial in a modern VM, due to cross-cutting. That's at least my experience from implementing the ones currently on the ES6 radar, proxies and symbols (not even considering optimisations). We should be very conservative about introducing more of these than absolutely necessary.

# Andreas Rossberg (11 years ago)

On 18 July 2013 18:16, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

There is nothing following this in the Symbols section of the minutes that address whether or not there are Symbol wrappers. My take-away from the meeting was we would try to do Symbols as as a primitive, with a factory object named 'Symbol', but no user visible wrapper instances. I don't see anything in the minutes that says otherwise.

I agree that the notes aren't explicit wrt wrapping, and I don't remember what exactly we discussed about that in particular. But my assumption has been that the factory also is the wrapper, and generally everything ought to be analogous to all other primitive types, except where we explicitly decided something else (such as toString throwing). It didn't occur to me that one might assume otherwise. ;)

In fact, the conclusion/resolution doesn't even say that Symbols will be primitive values. All of the bullet items listed there are apply equally to either symbols as primitives or symbols as exotic objects. The current (and previous, I believe) spec. draft reflects those explicit conclusions.

Ah, come on. That was the main point of the proposal, and it's explicit at the beginning of the notes. It was clear to everybody in the room, including you. You went to adopt it in the spec, after all.

# Andreas Rossberg (11 years ago)

On 18 July 2013 19:33, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Unfortunately, we don't even agree upon the details of the consensus. These consensus agreements at meetings are important, and I always try to get them into the spec. draft as soon as possible. However, they are typically made in the context of a relatively short "debate" where it is impossible for everybody to explore and understand all the deep implications of a decision. Sometimes we reach a consensus that latter proves to be problematic.

I have to take the result of those decisions and try to make them work in the spec. and implementors should be trying to make them work in their engines. I immediately incorporated the my understanding of the consensus into what was ultimately released as the May draft. After that I started to routinely discover numerous additional special cases in the spec. where primitive symbols had to be explicitly accounted for and also discovered that it was something that I was having to consider (or was forgetting to consider) as I added new material to the spec. My conclusion, was that primitive symbols without wrappers wasn't going to work.

I know I informally talked about this with several people. I reverted symbol to being an exotic object May 29 shortly after getting back from the May TC39 meeting so something that came out of that meeting pushed me over the edge towards reverting. I think it probably was things I ran into while changing function call to use [[Invoke]].

At a meta level, I need to keep moving forward on the spec. or it will never be completed. We seldom are able to definitively resolve issues on es-discuss and I can't let myself get blocked for up to two months waiting for a TC39 meeting. So I have to make make informed guesses about ultimate outcomes and use them in my working drafts so I can move forward with a reasonably self-consistent spec. Every thing in the draft spec is subject to (and needs) serious review by TC39 members. As I say quite often, nothing in the spec. if final until the entire thing is approved by TC39. (BTW, I really appreciate the stream of good feedback continue to get from the V8 team via bugs.ecmascript.org).

In this particular case, I guessed that we were going to ultimately either go back to Symbols as objects or to introduce wrapper objects for primitive Symbols. So, I had basically three choices: 1) keep going with special case handling of primitive symbols scattered through out the spec. 2) Revert to a centralized definition of exotic symbol objects, 3) add symbol wrappers.

option 1 was the maintenance nightmare I was trying to recover from. I believed there was strong opposition to adding additional wrapper objects, so I didn't want to push a Symbol wrapper into the spec. yet. But it turns out that option 2, was also a reasonable place for moving to option 3 if we decided to go that route (both option 2&3 eliminates most of the special case handling). So, that's where we stand today. I'm betting that TC39 will ultimately decide on either symbols as exotic objects or primitive symbols with wrappers. The current state of the spec. makes it easy for me to go either direction.

Sure, and I certainly don't blame you for trying to move things forward or to resolve ambiguities. But in cases like this I think it would have been appropriate to reach out to es-discuss or individual people first, like you did in other occasions (which I appreciate).

Anyway, we're all reasonable people, so let's leave it at that for meta discussion.

How do we move forward? Unfortunately, I won't make it to the meeting next week...

# Allen Wirfs-Brock (11 years ago)

On Jul 19, 2013, at 6:54 AM, Andreas Rossberg wrote:

On 18 July 2013 18:16, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

In fact, the conclusion/resolution doesn't even say that Symbols will be primitive values. All of the bullet items listed there are apply equally to either symbols as primitives or symbols as exotic objects. The current (and previous, I believe) spec. draft reflects those explicit conclusions.

Ah, come on. That was the main point of the proposal, and it's explicit at the beginning of the notes. It was clear to everybody in the room, including you. You went to adopt it in the spec, after all.

Oh, I agree with that. I was just making a point that the minutes aren't always a very reliable record of a discussion. And that's not meant as a criticism of Rick or Arv or anybody else who takes notes. I couldn't do it half as well as they do.

# Allen Wirfs-Brock (11 years ago)

On Jul 19, 2013, at 7:07 AM, Andreas Rossberg wrote:

How do we move forward? Unfortunately, I won't make it to the meeting next week...

At this point, the spec. is in a state where it is fairly easy to either stick with symbols as exotic objects or switch to symbols as a primitive type with a wrapper class.

From a language design perspective, I think the implications WRT future value types is the most important consideration and that is what we need to explore. Are we going to have to provide a mechanism for user defined primitive types + wrappers if we go that route?

I understand that in V8 you have cross-cutting implementation issues with symbols as objects. However there are also likely to be cross-cutting implementation issues for others relating to adding primitive types. I don't think either of these implementation perspectives should be the primary decision criteria. Instead, we should focus on the conceptual language design level. Which path will be better for the language and its users in the long run.

# Rick Waldron (11 years ago)

On Fri, Jul 19, 2013 at 11:48 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

On Jul 19, 2013, at 6:54 AM, Andreas Rossberg wrote:

On 18 July 2013 18:16, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

In fact, the conclusion/resolution doesn't even say that Symbols will be primitive values. All of the bullet items listed there are apply equally to either

symbols as primitives or symbols as exotic objects. The current (and previous, I believe) spec. draft reflects those explicit conclusions.

Ah, come on. That was the main point of the proposal, and it's explicit at the beginning of the notes. It was clear to everybody in the room, including you. You went to adopt it in the spec, after all.

Oh, I agree with that. I was just making a point that the minutes aren't always a very reliable record of a discussion. And that's not meant as a criticism of Rick or Arv or anybody else who takes notes. I couldn't do it half as well as they do.

Apologies for any ambiguity in the notes—I should've reiterated in the Conclusion/Resolution, but I distinctly remember that Andreas's proposal to make Symbols a new primitive was indeed accepted.

# Allen Wirfs-Brock (11 years ago)

On Jul 19, 2013, at 6:51 AM, Andreas Rossberg wrote:

On 18 July 2013 17:53, Brendan Eich <brendan at mozilla.com> wrote:

  1. Value object approach: no Symbol wrapper, typeof says "symbol", spec treats symbol as exotic object per latest draft.

The implementation cost of every new exotic object is fairly substantial in a modern VM, due to cross-cutting. That's at least my experience from implementing the ones currently on the ES6 radar, proxies and symbols (not even considering optimisations). We should be very conservative about introducing more of these than absolutely necessary.

You have to be able to support Proxy exotic objects so, I don't see why you won't use that exact mechanism for Symbol objects. In other words, use a self-hosted Proxy-based implementation for Symbol objects. The MOP operations on Symbol exotic objects in all cases either throw an exception or return some predetermined result such as undefined or false. These operations have little application utility (other than object model consistency) so there should be any perf. concerns about using a Proxy to define the symbol MOP behavior. Of course, you would still want to optimize for their use as property keys.

While there are a few standard exotic objects that clearly need optimization, I would hope that most future ones could reasonably be implemented using Proxy. After all, that was one of the primary motivations for Proxy. If Proxies are only toys then we have wasted an lot of time on them.

# Andreas Rossberg (11 years ago)

On 19 July 2013 18:17, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

You have to be able to support Proxy exotic objects so, I don't see why you won't use that exact mechanism for Symbol objects.

Because they are different. There is no useful unified mechanism for exotic objects, at least no efficient one. Every new form of exotic object will be a new special case. As I said, the nice spec-level MOP abstraction is largely irrelevant at the implementation level.

I'm speaking from V8 experience here, but I would be surprised if the situation is much different in other modern VMs.

In other words, use a self-hosted Proxy-based implementation for Symbol objects. The MOP operations on Symbol exotic objects in all cases either throw an exception or return some predetermined result such as undefined or false.

You can't use proxies for symbols -- they are special in parts of the semantics (and that includes their wrappers, if we want them to be special, too). And I doubt that you will be able to use them for other exotic objects we might come up with (e.g. value objects would have special equality behaviour that proxies can't simulate).

Even if you could, I highly doubt that proxy performance will ever be up for the task, at least not for an implementation cost that isn't much higher than the special casing.

# Allen Wirfs-Brock (11 years ago)

On Jul 19, 2013, at 9:31 AM, Andreas Rossberg wrote:

On 19 July 2013 18:17, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

You have to be able to support Proxy exotic objects so, I don't see why you won't use that exact mechanism for Symbol objects.

Because they are different. There is no useful unified mechanism for exotic objects, at least no efficient one. Every new form of exotic object will be a new special case. As I said, the nice spec-level MOP abstraction is largely irrelevant at the implementation level.

I'm speaking from V8 experience here, but I would be surprised if the situation is much different in other modern VMs.

In other words, use a self-hosted Proxy-based implementation for Symbol objects. The MOP operations on Symbol exotic objects in all cases either throw an exception or return some predetermined result such as undefined or false.

You can't use proxies for symbols -- they are special in parts of the semantics (and that includes their wrappers, if we want them to be special, too). And I doubt that you will be able to use them for other exotic objects we might come up with (e.g. value objects would have special equality behaviour that proxies can't simulate).

Note that I was talking about the symbols as exotic objects path, not the symbols as new primitive type path so there would be no wrappers. In what way are the object semantics of exotic symbol objects special such that couldn't be represented via a Proxy handler?

The special behavior of symbols appears, to me to be all ready of operations that are not part of the MOP. Hashing for property lookup, comparison during lookup, etc. I still don't see why the Proxy MOP dispatch mechanism wouldn't be perfectly adequate for their generally pointless application to symbols.

Even if you could, I highly doubt that proxy performance will ever be up for the task, at least not for an implementation cost that isn't much higher than the special casing.

Like I said, I don't see how this is a performance issue for Symbols exotic objects because the MOP operations are never important for them.

# Dean Landolt (11 years ago)

On Fri, Jul 19, 2013 at 12:31 PM, Andreas Rossberg <rossberg at google.com>wrote:

On 19 July 2013 18:17, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

You have to be able to support Proxy exotic objects so, I don't see why you won't use that exact mechanism for Symbol objects.

Because they are different. There is no useful unified mechanism for exotic objects, at least no efficient one. Every new form of exotic object will be a new special case. As I said, the nice spec-level MOP abstraction is largely irrelevant at the implementation level.

I'm speaking from V8 experience here, but I would be surprised if the situation is much different in other modern VMs.

In other words, use a self-hosted Proxy-based implementation for Symbol objects. The MOP operations on Symbol exotic objects in all cases either throw an exception or return some predetermined result such as undefined or false.

You can't use proxies for symbols -- they are special in parts of the semantics (and that includes their wrappers, if we want them to be special, too).

I'm curious how symbols differ semantically from null-prototype, empty, frozen objects? I can't think of any substantive differences other the power to act as object keys (and some seemingly insignificant details like toString behavior). If that's truly the case, wouldn't it be a lot easier to just allow any null-prototype, empty, frozen object to have the object-key capability?

For consistency Object.prototype.toString could even be specified to return something along the lines of [object Symbol] for values which fulfill this criteria (a breaking change, but only very slightly -- I can't imagine this affecting code in the wild).

# Brandon Benvie (11 years ago)

On 7/19/2013 9:52 AM, Allen Wirfs-Brock wrote:

Even if you could, I highly doubt that proxy performance will ever be up for the task, at least not for an implementation cost that isn't much higher than the special casing.

Like I said, I don't see how this is a performance issue for Symbols exotic objects because the MOP operations are never important for them.

Indeed, a largely self-hosted implementation of Symbol could look like:

const UNDEFINED = () => {};

const TRUE = () => true;

const FALSE = () => false;

const NULL = () => null;

const ARRAY = () => [];

const symbolHandler = {
   getOwnPropertyDescriptor: UNDEFINED,
   getOwnPropertyNames: ARRAY,
   getPrototypeOf: NULL,
   setPrototypeOf: FALSE,
   defineProperty: FALSE,
   deleteProperty: TRUE,
   freeze: TRUE,
   seal: TRUE,
   preventExtensions: TRUE,
   isFrozen: TRUE,
   isSealed: TRUE,
   isExtensible: FALSE,
   has: FALSE,
   hasOwn: FALSE,
   get: UNDEFINED
   set: FALSE,
   enumerate: ARRAY,
   keys: ARRAY,
};

function Symbol(name){
   const symbol = new Proxy({}, symbolHandler);
   %MarkAsSymbol(symbol, name);
   return symbol;
}

It's rare (and pointless) to perform MOP operations on a Symbol because they're inert. There's no need to optimize those. The optimization that has to happen is deciding when something is a symbol for the purposes of property lookup.

# Andreas Rossberg (11 years ago)

On 19 July 2013 18:52, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jul 19, 2013, at 9:31 AM, Andreas Rossberg wrote:

You can't use proxies for symbols -- they are special in parts of the semantics (and that includes their wrappers, if we want them to be special, too). And I doubt that you will be able to use them for other exotic objects we might come up with (e.g. value objects would have special equality behaviour that proxies can't simulate).

Note that I was talking about the symbols as exotic objects path, not the symbols as new primitive type path so there would be no wrappers. In what way are the object semantics of exotic symbol objects special such that couldn't be represented via a Proxy handler?

The special behavior of symbols appears, to me to be all ready of operations that are not part of the MOP. Hashing for property lookup, comparison during lookup, etc. I still don't see why the Proxy MOP dispatch mechanism wouldn't be perfectly adequate for their generally pointless application to symbols.

That would at least require some way of distinguishing ordinary proxies from those internal symbol proxies, so that they can cheaply be recognised as symbols. Alas, a separate proxy type, or extra internal info you need to store in every proxy.

At the same time, proxies already come with a space overhead that you don't want to pay for symbols.

No, I think that proxies would be total overkill here, and yet not even sufficient.

# Andreas Rossberg (11 years ago)

On 19 July 2013 19:41, Dean Landolt <dean at deanlandolt.com> wrote:

I'm curious how symbols differ semantically from null-prototype, empty, frozen objects?

They differ in that the extra cruft that's needed to represent random objects is a complete waste of space on them. Also, there is a performance benefit if they can share a common representation with strings, so that you don't need a case distinction in every place dealing with property names (e.g. wrt to hashing).

In any case, I don't want to focus exclusively on the implementation. I also think that there are obvious semantic and usability reasons for making them as similar to existing types as possible (esp strings, which they are closely related to).

I can't think of any substantive differences other the power to act as object keys (and some seemingly insignificant details like toString behavior). If that's truly the case, wouldn't it be a lot easier to just allow any null-prototype, empty, frozen object to have the object-key capability?

Unfortunately, making other objects into keys would break existing code that assumes a ToString conversion for those.

# Tab Atkins Jr. (11 years ago)

On Fri, Jul 19, 2013 at 1:48 PM, Andreas Rossberg <rossberg at google.com> wrote:

On 19 July 2013 19:41, Dean Landolt <dean at deanlandolt.com> wrote:

I'm curious how symbols differ semantically from null-prototype, empty, frozen objects?

They differ in that the extra cruft that's needed to represent random objects is a complete waste of space on them. Also, there is a performance benefit if they can share a common representation with strings, so that you don't need a case distinction in every place dealing with property names (e.g. wrt to hashing).

In any case, I don't want to focus exclusively on the implementation. I also think that there are obvious semantic and usability reasons for making them as similar to existing types as possible (esp strings, which they are closely related to).

I can't think of any substantive differences other the power to act as object keys (and some seemingly insignificant details like toString behavior). If that's truly the case, wouldn't it be a lot easier to just allow any null-prototype, empty, frozen object to have the object-key capability?

Unfortunately, making other objects into keys would break existing code that assumes a ToString conversion for those.

Thus Dean's suggestion of only allowing it for null-prototype, frozen, empty objects. These are exceedingly rare in the first place, because you have to jump through several hoops to create them.

(I don't think I like Dean's suggestion, but I don't want incorrect assumptions getting thrown about.)

# Dean Landolt (11 years ago)

On Fri, Jul 19, 2013 at 2:48 PM, Andreas Rossberg <rossberg at google.com>wrote:

On 19 July 2013 19:41, Dean Landolt <dean at deanlandolt.com> wrote:

I'm curious how symbols differ semantically from null-prototype, empty, frozen objects?

They differ in that the extra cruft that's needed to represent random objects is a complete waste of space on them. Also, there is a performance benefit if they can share a common representation with strings, so that you don't need a case distinction in every place dealing with property names (e.g. wrt to hashing).

In any case, I don't want to focus exclusively on the implementation. I also think that there are obvious semantic and usability reasons for making them as similar to existing types as possible (esp strings, which they are closely related to).

I completely agree that it makes sense to keep them as similar as possible to existing types. in fact, I'm just extending your argument a bit, but picking one nit: symbols are more like objects than strings.

Since they're only useful as keys what matters for our purposes is their unique, unforgeable identity, which they share in common with any other object. The only thing they have in common specifically with strings (and not other primitives) is the special capability to act as an object key. But again, what matters here is how -- their intensional identity is crucially different than the extensional identity of strings.

There's no reason I know of why this capability of being able to key an object property couldn't be extended from strings to objects -- provided, of course, that the objects are completely stateless and frozen. Isn't this the very definition of a Symbol? A symbol is just a stateless frozen object -- do we really care what it toString's to? Once an object is stateless and frozen it can't be anything but stateless and frozen, thus any stateless frozen object could just as well be a symbol. Sure, the language can spec a Symbol constructor to make it easier to mint stateless frozen objects, but is there any difference?

So if your ends is to keep symbols as close as possible to existing types I think suspect this approach does you one better -- it makes Symbols a perfect subtype of one of the built-ins -- just not the one you were thinking :)

I can't think of any substantive differences other the power

to act as object keys (and some seemingly insignificant details like toString behavior). If that's truly the case, wouldn't it be a lot easier to just allow any null-prototype, empty, frozen object to have the object-key capability?

Unfortunately, making other objects into keys would break existing code that assumes a ToString conversion for those.

Perhaps. I know it'd be breaking -- perhaps I'm not being imaginative enough about the kind of code it could break. I doubt I've written code that would break on this, but I've definitely written code that will break if the range from typeof is expended.

Regardless, I doubt it'd even be useful to hack the spec for a special toString. Is there any reason a symbol's toString couldn't return "[object Object]"? if this is the only drawback to normalizing symbols in the way I suggest, it seems like a small price when all the other quibbles being debated melt away.

# Allen Wirfs-Brock (11 years ago)

On Jul 19, 2013, at 11:37 AM, Andreas Rossberg wrote:

On 19 July 2013 18:52, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jul 19, 2013, at 9:31 AM, Andreas Rossberg wrote:

You can't use proxies for symbols -- they are special in parts of the semantics (and that includes their wrappers, if we want them to be special, too). And I doubt that you will be able to use them for other exotic objects we might come up with (e.g. value objects would have special equality behaviour that proxies can't simulate).

Note that I was talking about the symbols as exotic objects path, not the symbols as new primitive type path so there would be no wrappers. In what way are the object semantics of exotic symbol objects special such that couldn't be represented via a Proxy handler?

The special behavior of symbols appears, to me to be all ready of operations that are not part of the MOP. Hashing for property lookup, comparison during lookup, etc. I still don't see why the Proxy MOP dispatch mechanism wouldn't be perfectly adequate for their generally pointless application to symbols.

That would at least require some way of distinguishing ordinary proxies from those internal symbol proxies, so that they can cheaply be recognised as symbols. Alas, a separate proxy type, or extra internal info you need to store in every proxy.

Whether proxies are used or not, you still need to do make that distinction. An identify test on the proxy handler or target reference might be one cheap way to identify them. There are undoubtably many other possibilities.

At the same time, proxies already come with a space overhead that you don't want to pay for symbols.

No, I think that proxies would be total overkill here, and yet not even sufficient.

This little subthread started with with you wanting to minimize the complex of another implementation level exotic object type. This is one way to do it. There are space and time trade-offs to be made but it definitely is not an absurd approach to consider.

# Brandon Benvie (11 years ago)

Another factor I haven't seen mentioned but that I think is important is introducing a new primitive that has no literal form is another rough edge/inconsistency that will be confusing. Having to use a factory to make them makes me want to use new with that factory. But using new should always return an object from the builtins, ergo Symbols objects (whether wrappers or not) should be usable (either through auto-unwrapping or no primitive form) or some inconsistency or another will be introduced.

# Brendan Eich (11 years ago)

Brandon Benvie wrote:

Another factor I haven't seen mentioned but that I think is important is introducing a new primitive that has no literal form is another rough edge/inconsistency that will be confusing. Having to use a factory to make them makes me want to use new with that factory. But using new should always return an object from the builtins, ergo Symbols objects (whether wrappers or not) should be usable (either through auto-unwrapping or no primitive form) or some inconsistency or another will be introduced.

Allen's proposal from March, for Symbol and (I think) all scalar value objects, would be for new to throw.

Vector and struct value objects would be mutable if new'ed, immutable copy-semantics value otherwise.

# Allen Wirfs-Brock (11 years ago)

On Jul 19, 2013, at 6:02 PM, Brendan Eich wrote:

Brandon Benvie wrote:

Another factor I haven't seen mentioned but that I think is important is introducing a new primitive that has no literal form is another rough edge/inconsistency that will be confusing. Having to use a factory to make them makes me want to use new with that factory. But using new should always return an object from the builtins, ergo Symbols objects (whether wrappers or not) should be usable (either through auto-unwrapping or no primitive form) or some inconsistency or another will be introduced.

Allen's proposal from March, for Symbol and (I think) all scalar value objects, would be for new to throw.

No, that wasn't what I was trying to say in March. I was saying that if symbols were going to be non-object primitive values without a corresponding wrapper class and 'Symbol( )') was a function that produced such values, then 'new Symbol( )' should throw because because 'new' is the object creation operator and there would be no such objects to create.

On the other hand if symbols were exotic objects then 'new Symbols( )' would be the natural way to create them. We might choose to also let 'Symbol( )' act as a factory function for creating symbols even though I argue that using constructors as callable factories should be an ES6 anti-pattern.

If symbol are primitive values with a corresponding Symbol wrapper class (Andreas' preference) then for consistency with Number/String/Boolean 'new Symbol(sym)' assuming 'sym' is a primitive symbol value should create a wrapper object for 'sym'. Probably we would choose to make 'Symbol()' be a generator (normal English usage, not funciton*) of symbol values. That, however, is somewhat of a departure from the meaning of 'Number()', 'String()', or 'Boolean()' each of which returns a specific value (0, "", false) rather than generating new unique values

For, symbols I argue that the second alternative is the least anomalous because it threats symbols almost exactly as if defined as:

class Symbol extends null {
   static [@@create] () {
     return Object.freeze( %tagAsSymbol({__proto__: null}));
   }
   constructor() {
      if (!%hasSymbolTag(this)) return new Symbol();
   }
}

Maybe this analysis can be extended to other value objects. I'm in the 'new' is the preferred way to create objects camp. However, applying the same logic used for symbols is not all that straightforward. Consider if you are implementing such a class in ES, for example (very rough):

class BigNum extends ValueObject {
   constructor (value) {
       setPrivateState(this, new DigitVector(value)); //assume that DigitVector handles various numeric/sting types (including DigitVectors) as arguments
    }
    ...
    plusOperator (rhs) {
        return new BigNum(getPrivateState(this).addDigits(getPrivateState(rhs));
    }
    ...
}

You still need a way to instantiate the internal state that represents the value.

Vector and struct value objects would be mutable if new'ed, immutable copy-semantics value otherwise.

I don't know,

   'new Foo(args)'  create a mutable Foo object
   'Foo(args)'           create an immutable Foo object

isn't an idiom that we've had before

# Andreas Rossberg (11 years ago)

On 19 July 2013 21:19, Dean Landolt <dean at deanlandolt.com> wrote:

On Fri, Jul 19, 2013 at 2:48 PM, Andreas Rossberg <rossberg at google.com> wrote:

In any case, I don't want to focus exclusively on the implementation. I also think that there are obvious semantic and usability reasons for making them as similar to existing types as possible (esp strings, which they are closely related to).

I completely agree that it makes sense to keep them as similar as possible to existing types. in fact, I'm just extending your argument a bit, but picking one nit: symbols are more like objects than strings.

Since they're only useful as keys what matters for our purposes is their unique, unforgeable identity, which they share in common with any other object. The only thing they have in common specifically with strings (and not other primitives) is the special capability to act as an object key. But again, what matters here is how -- their intensional identity is crucially different than the extensional identity of strings.

Is it? That's largely a philosophical question. You can just as well view symbols as opaque values with extensional equality. In fact, that's a typical approach for user-defined implementations of the equivalent concept in other languages (e.g. as an ADT with an integer representation).

Arguing that everything with generative construction is an object because only objects currently have generative construction in JS seems like a rather circular argument.

There's no reason I know of why this capability of being able to key an object property couldn't be extended from strings to objects -- provided, of course, that the objects are completely stateless and frozen.

We can't, it's a breaking change.

Isn't this the very definition of a Symbol?

Only if you choose to make it so.

A symbol is just a stateless frozen object -- do we really care what it toString's to?

Yes. Implicit toString is a subtle foot gun for symbols. You can end up accidentally converting a symbol to a string somewhere and then installing a property with name "[object Symbol]" without noticing.

However, this question is rather independent from the primitive-vs-object discussion.

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

I don't know, 'new Foo(args)' create a mutable Foo object 'Foo(args)' create an immutable Foo object isn't an idiom that we've had before

Relax "mutable" in the first comment and remove "object" from the second comment and we have relevant precedent:

new Boolean(false) // create an extensible wrapper object
Boolean(false) // unobservably return false or create a new false

new Number(42) // create an extensible wrapper object
Number(42) // unobservably return the argument or create a new 42

new String('hi') // create an extensible wrapper object
String('hi') // unobservably return argument or create new 'hi'

The point about value objects to attend to here: their identity based on frozen contents.

(Why are they objects? Because everything's an object except for the legacy primitives.)

The truthiness of new Boolean(false) is a problem for numeric value objects, which my int64/uint64 prototype addresses by including boolean test among the operators that can be defined for value objects.

There's no perfect precedent. Falsy 'new Boolean(false)' was rejected in ES1 standardization because it implied a conversion from object to boolean, which might happen more than once for a given sub-expression due to || and && being value-preserving.

What's more important given JS's legacy than precedent: serving users by considering use-cases for value objects.

The use-case for mutable structs and vectors is clear from today's objects used for points, homogenous coordinates, rectangles, shapes, etc.

The use-case for immutable structs and vectors is clear from SIMD work under way in TC39, in JS extensions, in Dart.

The propose to serve both use-cases by specifying that 'new T(x)' constructs a mutable value object while calling 'T(x)' makes an immutable one aims to avoid clumsy alternative static method factories or differently named wrappers.

Debatable, but showing class BigNum extends ValueObject doesn't decide the question. We are introducing new semantics, starting with by-value identity rather than by-reference, and extending to operators and literals. We can't do this just using ES6 'class' syntax as-is.

# Allen Wirfs-Brock (11 years ago)

On Jul 20, 2013, at 4:14 PM, Brendan Eich wrote:

Relax "mutable" in the first comment and remove "object" from the second comment and we have relevant precedent:

 new Boolean(false) // create an extensible wrapper object
 Boolean(false) // unobservably return false or create a new false

 new Number(42) // create an extensible wrapper object
 Number(42) // unobservably return the argument or create a new 42

 new String('hi') // create an extensible wrapper object
 String('hi') // unobservably return argument or create new 'hi'

Except that:

new Boolean(false) === false //false   similarly String and Number
Boolean(false) === false          //true    similarly String and Number

so the difference between wrappers and primitive values is observable. It isn't observable whether there is a single or multiple heap element for each logically === equivalent primitive value/

The point about value objects to attend to here: their identity based on frozen contents.

(Why are they objects? Because everything's an object except for the legacy primitives.)

The truthiness of new Boolean(false) is a problem for numeric value objects, which my int64/uint64 prototype addresses by including boolean test among the operators that can be defined for value objects.

There's no perfect precedent. Falsy 'new Boolean(false)' was rejected in ES1 standardization because it implied a conversion from object to boolean, which might happen more than once for a given sub-expression due to || and && being value-preserving.

I think the truthiness of 'new Boolean(false)' is a one-off special case that we shouldn't worry about as a precedent. I don't believe there are equivalent issues with String or Number.

What's more important given JS's legacy than precedent: serving users by considering use-cases for value objects.

The use-case for mutable structs and vectors is clear from today's objects used for points, homogenous coordinates, rectangles, shapes, etc.

The use-case for immutable structs and vectors is clear from SIMD work under way in TC39, in JS extensions, in Dart.

The propose to serve both use-cases by specifying that 'new T(x)' constructs a mutable value object while calling 'T(x)' makes an immutable one aims to avoid clumsy alternative static method factories or differently named wrappers.

This would be a new idiom, and one that wouldn't necessarily apply to non-structured objects. This is a refactoring hazard if someone starts with a normal object and decides to re-implement as a struct-based object.

Since this is a new idiom, other new idioms could be considered. For example:

new T(x) //create a mutable instance:
T.value(x) //create an immutable instance

bikesheding starts here...

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

Except that:

new Boolean(false) === false //false   similarly String and Number
Boolean(false) === false          //true    similarly String and Number

so the difference between wrappers and primitive values is observable.

Sure, legacy crap we must not copy into new types. Right?

It isn't observable whether there is a single or multiple heap element for each logically === equivalent primitive value/

I explicitly addressed truthy ToObject later. Did you miss it? Boolean is a terrible precedent for value objects, which must include 0 when numeric (int64, uint64, bignum, decimal, rational, etc.).

I think the truthiness of 'new Boolean(false)' is a one-off special case that we shouldn't worry about as a precedent. I don't believe there are equivalent issues with String or Number.

Sure: "" is falsy but new String("") is truthy; 0 and NaN are falsy but new Number(0), e.g., is truthy.

Ok, so a three-off special case-set we should not imitate with value objects in general. Right?

This would be a new idiom, and one that wouldn't necessarily apply to non-structured objects. This is a refactoring hazard if someone starts with a normal object and decides to re-implement as a struct-based object.

If you are arguing that constructors must not do something other than construct when called, let's have that discussion separately. It's a general fly in your refactoring ointment -- and has been forever in JS.

Since this is a new idiom, other new idioms could be considered. For example:

new T(x) //create a mutable instance:
T.value(x) //create an immutable instance

bikesheding starts here...

I thought about such things but it's not only a matter of bikeshedding. Usability comes first and is not all about aeshetics. Say we add int64. To convert to it, must I call

int64.value(x)

and not

int64(x)

merely to preserve some object-idiom idiocy that no one wants for int64, namely:

new int64(x) // throws, does not make a mutable object

?

# Allen Wirfs-Brock (11 years ago)

On Jul 22, 2013, at 3:25 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

On Jul 20, 2013, at 4:14 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

right, to all of the above.

This would be a new idiom, and one that wouldn't necessarily apply to non-structured objects. This is a refactoring hazard if someone starts with a normal object and decides to re-implement as a struct-based object.

If you are arguing that constructors must not do something other than construct when called, let's have that discussion separately. It's a general fly in your refactoring ointment -- and has been forever in JS.

Yes, I agree that is a separate discussion. We already have constructors that do different things and it remains possible to define them using ES6 class declarations regardless of whether or not it ends up being considered a pattern or an anti=pattern.

Since this is a new idiom, other new idioms could be considered. For example:

new T(x) //create a mutable instance:
T.value(x) //create an immutable instance

bikesheding starts here...

I thought about such things but it's not only a matter of bikeshedding. Usability comes first and is not all about aeshetics. Say we add int64. To convert to it, must I call

int64.value(x)

and not

int64(x)

merely to preserve some object-idiom idiocy that no one wants for int64, namely:

new int64(x) // throws, does not make a mutable object

My concern is that the pattern

new T(x) //create a mutable instance:
T(x) //create an immutable instance

is a new one that we really don't reflect current usage in either the specification or in the wild, eg RegExp, Date. Also Map/Set as currently implemented in FF, but that is also a seperate (but related) discussion.

I agree that int64(x) is nice for int64 (although I would expect such scalar values to me immutable regardless of how you create them).

It is also appealing for structs but not necessarily for objects which means it is hard to know when you see 'new Foo(x)' vs 'Foo(x)' whether the mutable/immutable pattern applies or not for Foo.

Presumably Structs are subclassable. But, where I see the real problem is if we wanted to support class declarations that use Structs as their private state. We would still want such classes to be subclassable and super calls of the constructor would still be needed for initialize both mutable and immutable subclasses.

It seems like the real usability challenge is finding a scheme that is pleasant for both scalars and potentially subclass able objects.

Here another stab at the int64 use case.

let int64 = (...args) => Object.freeze(new Builtins.Int64(...args));
  // int64 is a non-constructable function. It is the normal way of creating Int64 instances
  // Builtins.Int64 is (at least conceptually) a normal constructor that uses 'new' to create instances but would seldom be directly used in that manner
  // For Int64 the Object.freeze may actually be redundant but wouldn't necessarily be so if the pattern was extended to structured data.
# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

My concern is that the pattern

new T(x) //create a mutable instance:
T(x) //create an immutable instance

is a new one that we really don't reflect current usage in either the specification or in the wild, eg RegExp, Date.

Those are definitely not value objects. More's the pity, perhaps, but too late.

Also Map/Set as currently implemented in FF, but that is also a seperate (but related) discussion.

Definitely not value objects.

I agree that int64(x) is nice for int64 (although I would expect such scalar values to me immutable regardless of how you create them).

The intuition (supported by other languages) is that 'new' heap-allocates something mutable by default. Stack allocation and (implicit or not) coercion does not. C++ is not far from the mark here, but IIRC C# is similar.

It is also appealing for structs but not necessarily for objects which means it is hard to know when you see 'new Foo(x)' vs 'Foo(x)' whether the mutable/immutable pattern applies or not for Foo.

Indeed, structs are new in this sense. I don't think T.value(x) helps, though.

Presumably Structs are subclassable. But, where I see the real problem is if we wanted to support class declarations that use Structs as their private state. We would still want such classes to be subclassable and super calls of the constructor would still be needed for initialize both mutable and immutable subclasses.

That's a challenge to rise to ;-).

It seems like the real usability challenge is finding a scheme that is pleasant for both scalars and potentially subclass able objects.

Here another stab at the int64 use case.

let int64 = (...args) => Object.freeze(new Builtins.Int64(...args));
  // int64 is a non-constructable function. It is the normal way of creating Int64 instances
  // Builtins.Int64 is (at least conceptually) a normal constructor that uses 'new' to create instances but would seldom be directly used in that manner
  // For Int64 the Object.freeze may actually be redundant but wouldn't necessarily be so if the pattern was extended to structured data.

I think this goes in the wrong direction. 'new' implies heap allocation and reference-type semantics. Value objects do not have either.

# Allen Wirfs-Brock (11 years ago)

On Jul 22, 2013, at 6:30 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

...

I agree that int64(x) is nice for int64 (although I would expect such scalar values to me immutable regardless of how you create them).

The intuition (supported by other languages) is that 'new' heap-allocates something mutable by default. Stack allocation and (implicit or not) coercion does not. C++ is not far from the mark here, but IIRC C# is similar.

pretty much agree, except for the mutable part. There is no reason that a heap allocated entity can't be immutable, by default if it is appropriate for the abstraction. Similar,it is certainly possible to design a language with stack allocated mutable structs.

# Brendan Eich (11 years ago)

Allen Wirfs-Brock wrote:

On Jul 22, 2013, at 6:30 PM, Brendan Eich wrote:

Allen Wirfs-Brock wrote:

... I agree that int64(x) is nice for int64 (although I would expect such scalar values to me immutable regardless of how you create them). The intuition (supported by other languages) is that 'new' heap-allocates something mutable by default. Stack allocation and (implicit or not) coercion does not. C++ is not far from the mark here, but IIRC C# is similar.

pretty much agree, except for the mutable part. There is no reason that a heap allocated entity can't be immutable, by default if it is appropriate for the abstraction.

Ok, but you are picking a nit.

Similar,it is certainly possible to design a language with stack allocated mutable structs.

Let's not.

On the heap-allocated immutable bit, you'd need to say something extra to "opt in". That's JS!

# Brendan Eich (11 years ago)

Brendan Eich wrote:

I think this goes in the wrong direction. 'new' implies heap allocation and reference-type semantics. Value objects do not have either.

Previously, from Claude Pache in April: esdiscuss/2013-April/029750


The idea is simply to avoid adding footguns for new features, even if we can't remove the existing ones.

Regarding the issue discussed here, let me elaborate:

Until now, new Primitive, where Primitive is Number, String or Boolean is, in practice, not a problem, because you never need to create a new number, string or boolean that way. (You can use Primitive as a function for typecasting, but it is not felt as the same thing as creating a new value.) Things are different with symbols, so the new Symbol footgun is practically a new type of footgun.

Note also that new Date, new RegExp, new Map, etc., work as intuitively expected, and, if I have correctly followed the last discussions, function* gen() { /* ... */}; myIterator = new gen (meaning: "I want to get a new iterator from that generator function") would also work.

I do agree that special-casing should be avoided. Therefore, I think we should make a rule: new Primitive should throw for primitive types, except for legacy numbers, booleans and strings (Don't Break The Web) for which it is not a real issue.