What should `Symbol(sym)` do?

# Claude Pache (11 years ago)

According to the current version of the ES6 spec, Symbol(desc), when desc is not undefined, coerces its argument to a string and uses it as a description for a newly created symbol. In particular, if sym is a symbol, Symbol(sym) throws a TypeError (it can't be coerced to string).

Intuitively, I would expect that Symbol(sym) just returns sym, just like Number(num) returns num, or, more generally, just like Primitive(x) casts its argument to the corresponding primitive.

What do you think?

# Rick Waldron (11 years ago)

Intuitively, I wouldn't expect anything else but this--I think this is just a minor authoring oversight. Please file at bugs.ecmascript.org

# Allen Wirfs-Brock (11 years ago)

On Feb 11, 2014, at 6:40 AM, Claude Pache wrote:

According to the current version of the ES6 spec 1, Symbol(desc), when desc is not undefined, coerces its argument to a string and uses it as a description for a newly created symbol. In particular, if sym is a symbol, Symbol(sym) throws a TypeError (it can't be coerced to string).

Intuitively, I would expect that Symbol(sym) just returns sym, just like Number(num) returns num, or, more generally, just like Primitive(x) casts its argument to the corresponding primitive.

What do you think?

I can't find any explicit mention of this in the meeting notes 1 where we decided to make Symbol a primitive type with a wrapper class. However, I made the appropriate changes to the spec. immediately after that meeting when the discussion was fresh in my mind and I still think that throwing on Symbol(sym) is the right thing to do:

For the other primitive/wrapper types the following conventions apply:

new Wrapper(prim)   //always creates a new Wrapper instances wrapping the supplied primitive value 
Wrapper(prim)            //always returns a primitive value derived by coercing the argument to the appropriate primitive type. Returns prim if same type.

At 1 we made different decisions for symbols and Symbol wrappers :

  new Symbol(arg)     // always throws, we don't support explicit creation of Symbol wrapper objects 
  Symbol(arg)              // always returns a new, not previously observed, primitive symbol value

We made new Symbol throw to avoid the silent mistake where somebody uses it thinking they are actually creating a new symbol value rather than a wrapper object. Instead we decide to make a direct call to Symbol the only way (other than Symbol.for) to access a new previously unobserved symbol value.

Symbol("string") is a symbol factory call, not a coercion of "string" to a symbol. The argument is not a value to be coerced to symbol but a string value that is part of the state of the new symbol value. In particular:

   console.log(Symbol("x") === Symbol("x"))  //false, each call to Symbol returns a new unique symbol value

If Symbol(sym) returned sym, that would break the invariant that calling Symbol always produces a new symbol value. Rather that reenforcing the fact the Symbol has its own usage patterns that are different from Number/String/Boolean it would partially blur that distinction.

ToString(symbolValue) throws because we don't want people doing string concatenation to a symbol thinking they are manipulating a string property key.

ToString(symbolWrapper) just does a normal symbolWrapper.toString() call.

It would be consistent with the use of Symbol(foo) to allow Symbol(symbolValue) and to internally perform symbolValue.toString() [note implicit wrapping via property access) . However, that would still create a new unique symbol value which probably isn't what the programmer actually intended. Another silent mistake.

When all these factors are considered, I think what is currently specified is just fine.

# Brandon Benvie (11 years ago)

One consideration is that it still is possible end up with a Symbol wrapper and with the current spec there's no way to unwrap it. The argument for having a Symbol wrapper at all was to enabled prototypal inheritance, so, in theory, we want to enable people to add methods to Symbol.prototype. When non-strict methods on Symbol.prototype are called, they'll encounter a wrapped Symbol object that they can't do much with since they can never get at the underlying Symbol primitive.

# Allen Wirfs-Brock (11 years ago)

On Feb 11, 2014, at 11:20 AM, Brandon Benvie wrote:

One consideration is that it still is possible end up with a Symbol wrapper and with the current spec there's no way to unwrap it.

Symbol.prototype.valueOf returns the primitive value of a Symbol wrapper (note, I just fixed it in rev 23 so it and toString also work for unwrapped symbol value)

# Brandon Benvie (11 years ago)

Ah ignore me then!

# Claude Pache (11 years ago)

Le 11 févr. 2014 à 20:04, Allen Wirfs-Brock <allen at wirfs-brock.com> a écrit :

Symbol("string") is a symbol factory call, not a coercion of "string" to a symbol. The argument is not a value to be coerced to symbol but a string value that is part of the state of the new symbol value. In particular:

console.log(Symbol("x") === Symbol("x"))  //false, each call to Symbol returns a new unique symbol value

If Symbol(sym) returned sym, that would break the invariant that calling Symbol always produces a new symbol value. Rather that reenforcing the fact the Symbol has its own usage patterns that are different from Number/String/Boolean it would partially blur that distinction.

Good point. Symbol(x) consistently produces a different value at each call, whereas for other primitives, Primitive(x) consistently produces the same value.

If we wanted absolutely to be consistent with other primitives, we could make Symbol(...) a function that casts to symbol (and, in fact, throws a TypeError in most cases), and use another function (e.g., Symbol.spawn("name")) in order to produce new symbols. But it is probably not worth to do that.