Sunday, July 4, 2021

JavaScript's Symbol.toPrimitive confuses me

JavaScript has all these built-in Symbols that can be used for various meta-programming tasks.  Symbol.toPrimitive is a method on your object that gets called by the JavaScript engine when it needs to convert that object to a primitive value.  It works by passing a string-valued hint to this function, this hint can be "number", "string", or "default".

Implementing it is quite easy.  Just switch() on the hint and define your preferred logic for the three hints in their respective cases.

The confusing part is that JavaScript doesn't seem to have much in the way of logic behind what it chooses to pass for the hint.  Initially, "number" and "string" seem obvious and the source of confusion is "what the hell do I do when it passes "default"?", but quickly the source of confusion becomes "I would have expected it to pass literally anything other than "default" in this case!".

To illustrate this, here's an example:
class MyClass { [Symbol.toPrimitive]( hint ) { switch( hint ) { case "number": return 42; case "string": return "meaning of life"; case "default": return "the two best words in the English language"; } } } var a = new MyClass(); console.log( +a ); // Hint: number; logs 42 console.log( a + 27 ); // Hint: default; logs "the two best words in the English language27" console.log( a + "" ); // Hint: default; logs "the two best words in the English language" console.log( `${a}` ); // Hint: string; logs "meaning of life"
Unary plus getting the hint "number" is all well and good.  The template string converting a to a string and therefore getting the hint "string" is just fine and dandy.  That's also the only case I know of where JavaScript provides the hint "string".

What makes absolutely zero sense, however, are the other two.  When adding with a number, I would expect it to provide the hint "number", but instead we get "default".  Adding with a string also produces the hint "default" when I would have expected "string".

So uh... what the fuck?

In case this is a browser thing, I'm using Pale Moon 28.17.  I haven't updated to 29.* because it introduces breaking changes to my setup that I haven't resolved yet.