Implicit coercion of Symbols
One reason it might make sense to throw, is people converting values to string names for use as object properties. Reason you'd want to throw would be to prevent accidentally making the key useless (different from its original Symbol value).
Haven't paid attention to the rationale, but that doesn't seem like a bad one.
Caitlin Potter wrote:
One reason it might make sense to throw, is people converting values to string names for use as object properties. Reason you'd want to throw would be to prevent accidentally making the key useless (different from its original Symbol value).
This is exactly the reason.
Of course, having String(x) and '' + x diverge is funky, but not novel:
js> o = {valueOf(){return 42}, toString(){return 'haha'}}
({valueOf:function valueOf(){return 42}, toString:function
toString(){return 'haha'}})
js> String(o)
"haha"
js> ''+o
"42"
On Fri Jan 02 2015 at 7:53:22 PM Brendan Eich <brendan at mozilla.org> wrote:
This is exactly the reason.
Yep, I just wanted to make sure the subject got some last minute airtime to make sure this is really the way to go. I'll play the opposition here: is the hazard as compelling now as it was when it was first discussed? Now that implementors have had some time to work with Symbol, do proponents of "throw" still feel strongly?
Of course, having String(x) and '' + x diverge is funky, but not novel:
Sure, but the argument was re: the implicit coercion of built-ins.
Playing devil’s advocate: How realistic a danger is this? Do people ever compose a property key for an object out of several pieces?
It does add a fair amount of complexity for something that doesn’t seem that common.
On 1/2/15 9:33 PM, Axel Rauschmayer wrote:
Do people ever compose a property key for an object out of several pieces?
On the web? All the time.
Can you give an example?
On 1/2/15 9:40 PM, Axel Rauschmayer wrote:
Can you give an example?
get: function( num ) {
return num != null ?
// Return just the one element from the set
( num < 0 ? this[ num + this.length ] : this[ num ] ) :
// Return all the elements in a clean array
slice.call( this );
},
That's from jQuery 2.1.3.
And from the same place:
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
if ( keys.push( key + " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return (cache[ key + " " ] = value);
}
That's after looking through about 1/10 of the library. I'll bet there are more. I'll also bet this sort of thing appears in every single major library out there.
Arrays are a good point, this is where I’d think accidental coercions are most likely. The other use case is object-as-dictionary, which will slowly be replaced by Object.create(null)
(no need to escape in ES6+) and Map
.
I don’t feel strongly either way, I just feel that the added spec complexity is not ideal. Especially ToBoolean() not throwing an exception, while ToString() and ToNumber() do.
None of the objects in the examples bz cited are Arrays -- what did you mean?
Symbol must not implicitly convert in any object computed-property access to string.
On 03 Jan 2015, at 19:52, Brendan Eich <brendan at mozilla.org> wrote:
None of the objects in the examples bz cited are Arrays -- what did you mean?
When I though of +
being used inside square brackets, I only thought of strings, not of numbers (first example).
In the first example, this
is at least array-like. Otherwise, slice
wouldn’t work.
Sure, but you wrote "Arrays", not array-likes. Also bracketing for computed property access works with any object, not just array-likes.
Sorry to be nit-picky but precision required here ;-).
Right, but regardless of how the potential footgun is loaded, the exception is the divergence. That specifically is what I want us to look at—is this the right thing to do here? Currently, user code can do things like this:
var a = {};
//... sometime later...
a[o + "_idfoobarwhatever"] = "my important stuff";
And even if o
isn't a what the author expected, execution proceeds. I
still think that the exception is a good path forward, but I may not be
right about that.
The exception...
A. Requires user code to be more thoroughly tested—which is a good thing,
but inexperienced programmers won't think so.
B. Diverges from the well known implicit coercion behaviors that the
language has had for 20 years (@Brendan: sorry for the reminder ;). This is
arguably both a good thing and a bad thing (C, D, E, F)
C. Prevents user code from doing the wrong thing (assuming that A is
adhered to).
D. Produces a surprising behavior (in comparison to all other implicit
coercion operations)
E. W/r to A and C, most code will need some kind of guard, just to avoid
the exception, ie. either try/catch or if (typeof s !== "symbol") ...
F. Not directly related, but querySelectorAll(...) throws if the selector
is invalid, which is widely considered a poor design choice for that API.
This is something that all DOM-centric libraries paper over, because
authors would prefer that an invalid selector simply produce the equivalent
of "no found elements", than throwing an exception.
Subjectively: I think it's nice in theory, but bad in practice.
Rick Waldron wrote:
Subjectively: I think it's nice in theory, but bad in practice.
Compared to what? Converting a symbol to asilent-but-deadly string?
I thought you were gonna raise the other way to restore consistency: make String(sym) throw too. Then we'd need a new Reflect op to get a string from a symbol.
Sorry, that was poorly delivered—I was editorializing on the benefit of exception vs consistency vs expectation. I still agree that silent-but-deadly string issue is problematic, but it seems like an edge case for Symbols. Yes, plenty of code concatenates strings and/or numbers to make an object key or an array index; but just like "[object Foo]42" or "undefined42", "Symbol(description)42" is a obvious mistake and has the same bad outcome as the preceding two. The exception sucks and silent strings suck. I don't know which sucks less ;)
Agreed with Brendan, and I've thought the same.
It's been also years we have problems using unknonw objects in the wild, i.e.
var n = {__proto__:null};
var s = String(n); // error, No default value
var s = '' + n; // error, No default value
Same if used as [property accessor] so I am not sure why String(o)
when
o
is Symbol
should not throw.
I'd expect it to throw all the time and let developers be a bit more careful on their explicit or implicit casts.
The toString
and valueOf
are great examples that could compromise a lot
of real-world code.
Being Symbols
new we should probably/hopefully get them in the right way.
I feel like throwing either cases is the right way.
Just my thoughts, Best
That example above is pretty compelling for throw always consistency.
With a new Reflect.* API for converting a symbol to its diagnostic/debugging string?
If you agree, please file a bugs.ecmascript.org ticket.
On Jan 3, 2015 3:38 PM, "Rick Waldron" <waldron.rick at gmail.com> wrote:
E. W/r to A and C, most code will need some kind of guard, just to avoid
the exception, ie. either try/catch or if (typeof s !== "symbol") ...
This isn't a cost introduced by the throwing behavior, it's one that'll have to happen in code regardless of what we decide, or else the code will be incorrect. You can never construct modified keys from a Symbol by concatenation, even if it stringifies.
So your code either needs to be sure it won't be passed a Symbol, or else it needs to branch its behavior.
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20150104/0ad1ec00/attachment
Does it have to be a Reflect.* method? It could be Symbol.prototype.getDescription()
or a getter.
Given that there is currently no way to get a string representation of a value in a guaranteed side-effect-free fashion, I think something generalized for all primitive and object types would be ideal.
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20150104/a9ead304/attachment-0001
On Sat Jan 03 2015 at 9:41:57 PM Alex Kocharin <alex at kocharin.ru> wrote:
Also, if you want to prevent mistakes like
object['blah' + symbol]
, linters could be changed to forbid/warn about concatenation inside property names.
How would a linter know that symbol
was actually a Symbol?
On 1/3/15 9:41 PM, Alex Kocharin wrote:
function log(anything) { process.stdout.write(new Date().toJSON() + ' - ' + anything + '\n') }
Right now it'll never throw. Well, unless you override
.toString()
to throw, which I've never seen to be done intentionally.
Just to nitpick, try:
console.log("" + HTMLAnchorElement.prototype);
in your favorite browser and watch it throw precisely for this reason.
Or load
data:text/html,<iframe src="http://www.ecma-international.org/"></iframe>
and try:
console.log("" + frames[0]);
With throwing Symbols we'll have innocuous-looking code which will cause an exception.
The innocuous-looking code will totally cause exceptions in the wild today depending on what you pass to it.
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20150104/dca15205/attachment
Alex Kocharin wrote:
04.01.2015, 05:44, "Rick Waldron" <waldron.rick at gmail.com>:
How would a linter know that
symbol
was actually a Symbol?It wouldn't. But if it warns about string concatenation inside square braces (means, string + variable, but not number + variable), it should be good enough. This is the error the spec is trying to prevent, isn't it?
No. See the JQuery examples that bz provided.
My point is: concatenating Symbols with other strings have legitimate uses.
Name one.
And javascript shouldn't require any explicit type casting in order to do this, it isn't a statically typed language.
Static types have nothing to do with getting an error on implicit conversion.
An HTML attachment was scrubbed... URL: esdiscuss/attachments/20150104/ce1d7479/attachment-0001
Alex Kocharin wrote:
Name one.
I did name one in another message. Logging.
That's a use-case for some way (could be concatenation, but as noted the downside risk is huge; could be a new Reflect method) to convert symbol to string. Explicit is better than implicit. Saying "Logging" does not say "allow implicit symbol to string conversion".
I agree that String(sym) working where ''+sym throws is funky (my word in this thread). We could make both throw, with a Reflect.symbolToString or whatever it might be called. ES6 is about out of time, this may not fly, but if it is possible it has to be done quickly.
Alex, like I've said:
log({__proto__:null})
and welcome crashes of the server, if that's a real
concern in this form.
Is it that wrong to assume that if console.log({__proto__:null})
is
capable of handling non Objects and print a meaningful output, so should
any other logger implemented on user-land?
'cause logging a part there's still no case where implicit or explicit Symbol cast makes any sense.
It's like object['undefined']
where undefined
is implicitly created by
some missed property which has been somehow problematic for long time.
A generic description-aware Reflect.describe(genericObject)
like approach
seems way more useful for logging annd debugging purpose than a footgun in
the wild.
Le 4 janv. 2015 à 03:18, Rick Waldron <waldron.rick at gmail.com> a écrit :
I think that it is asking for the wrong consistency. There is another consistency to be wanted, namely:
String(sym) === sym.toString()
(assuming that Symbol.prototype.toString
isn't sabotaged by user code, of course). Indeed:
String(sym)
andsym.toString()
are explicit coercions to string; whilesym + ""
triggers an implicit coercion to string (in that very case, preceded by a coercion to primitive, but it's not the subject).
Now, it will be argued that it would be a precedent to make such a distinction between implicit and explicit coercion, for, until ES5, there is none. But, precisely, pervasive implicit coercion is often thought to be a mistake in the design of JavaScript (it hides bugs), while coercion in general is indeed useful (e.g., for debugging purpose, as pointed Alex in this thread). Now, as we are evolving the language, it is good to limit the scope of the bad implicit coercion behaviour (such as the abstract operation ToString()
of the spec), but to consolidate the functionality of the existing explicit coercion functions (such as String()
).
If there is a need to directly expose the implicit coercion to string operation to user code, Reflect.toString()
is a natural fit for that, together with Reflect.toBoolean()
, Reflect.toPropertyKey()
, Reflect.toPrimitive(_, hint)
, etc.
Claude Pache wrote:
Now, it will be argued that it would be a precedent to make such a distinction between implicit and explicit coercion, for, until ES5, there is none.
Kind of there all along, as noted up-thread:
js> o = {valueOf(){return 42}, toString(){return 'haha'}}
({valueOf:function valueOf(){return 42}, toString:function
toString(){return 'haha'}})
js> String(o)
"haha"
js> ''+o
"42"
But I take your point.
But, precisely, pervasive implicit coercion is often thought to be a mistake in the design of JavaScript (it hides bugs), while coercion in general is indeed useful (e.g., for debugging purpose, as pointed Alex in this thread).
Explicit coercion in general is useful. What's "explicit"? It could be console.log is an explicit-enough gesture. It does more than just ToString on its parameters today, IINM (at least in some browsers).
Now, as we are evolving the language, it is good to limit the scope of the bad implicit coercion behaviour (such as the abstract operation
ToString()
of the spec), but to consolidate the functionality of the existing explicit coercion functions (such asString()
).
Yes, this is the rationale for ES6's symbol handling today.
If there is a need to directly expose the implicit coercion to string operation to user code,
Reflect.toString()
is a natural fit for that, together withReflect.toBoolean()
,Reflect.toPropertyKey()
,Reflect.toPrimitive(_, hint)
, etc.
Yup; ES7 fodder at this stage.
I think it's very unlikely anyone will try to patch ES6 over the trade-offs among consistencies that this thread has illuminated. Thanks,
A good name for such a getter would be, IMO,
Symbol.prototype.description
. It would also seem to fit with some other
common JS idioms, such as Array.prototype.length
, etc. It also feels
clearer and cleaner as a getter than an instance method or function.
I re-read through this whole thread and realized nobody brought up the fact that this specific change, of making String(symbol)
work while symbol+""
throws, was discussed and agreed upon previously:
- esdiscuss.org/topic/string-symbol
- tc39/tc39-notes/blob/master/es6/2014-09/sept-23.md#41-spec-status-report
I realize people are presumably having second thoughts, but I thought it'd be worth linking to the previous thread for anyone who hasn't seen it and thinks this is a new debate.
Indeed, in my first reply on-thread, I wrote
"This is exactly the reason."
to acknowledge existing consensus. Thanks for linking!
From: Domenic Denicola <d at domenic.me> To: Rick Waldron <waldron.rick at gmail.com>, es-discuss <es-discuss at mozilla.org> Cc: Date: Mon, 12 Jan 2015 18:02:17 +0000 Subject: RE: Implicit coercion of Symbols I re-read through this whole thread and realized nobody brought up the fact that this specific change, of making
String(symbol)
work whilesymbol+""
throws, was discussed and agreed upon previously:
- esdiscuss.org/topic/string-symbol
- tc39/tc39-notes/blob/master/es6/2014-09/sept-23.md#41-spec-status-report
I realize people are presumably having second thoughts, but I thought it'd be worth linking to the previous thread for anyone who hasn't seen it and thinks this is a new debate.
Good catch. I wouldn't have been surprised if nobody even thought of it until now (in this discussion, anyways).
Kyle Simpson brought this up on Twitter today and I think it deserves one last look. Here's an example of the issue:
var sym = Symbol("description"); sym + ""; // Throws
Meanwhile...
var sym = Symbol("description"); String(sym); // "Symbol(description)" *
(* appears to be the convention that implementors have converged on)
This is the only time that a "thing" in JavaScript throws when it encounters an implicit coercion operation. This detail appears to be problematic in that it's an unnecessary divergence from the language's normal behaviour.
Ref: people.mozilla.org/~jorendorff/es6-draft.html#sec-addition-operator-plus-runtime-semantics-evaluation 11.a