Search code examples
javascriptecmascript-6symbols

Why, in ECMAScript, are symbols created with the same description not the same symbol, unlike most other languages with a “symbol” primitive?


I've been around the block when it comes to languages, having worked with everything from C# to Lisp to Scala to Haskell, and in every language that supported them, symbols have acted pretty much the same; that is, any two symbols with the same name, are guaranteed to be identical because they're singleton objects.

Racket: (equal? 'foo 'foo) true

Common Lisp: (eq 'foo 'foo) true

Ruby: :foo == :foo true

Scala: 'foo == 'foo true

ES6: Symbol('foo') === Symbol('foo') false

The benefit of symbols being singletons is obvious: You can use them in maps/dictionaries without risking your key not being equal to your input because the language suddenly decided to hash it differently (looking at you, Ruby)

So why is it that ECMAScript 6 takes a different approach on this, and how can I get around it?


Solution

  • You can (sort-of) get the effect of symbols being "knowable" by name by using registered (global) symbols:

    var s1 = Symbol.for("foo");
    var s2 = Symbol.for("foo");
    s1 === s2; // true
    

    Of course you can create your own Symbol registry too, with a Map instance or a plain object.

    edit — I'll add that the intention of the optional string parameter when making a new Symbol instance is to provide a way of identifying the meaning and purpose of a symbol to the programmer. Without that string, a Symbol works just fine as a Symbol, but if you dump out an object in a debugger the properties keyed by such anonymous Symbol instances are just values. If you're keeping numeric properties on an object with Symbol keys, then you'll just see some numbers, and that would be confusing. The description string associated with a Symbol instances gives the programmer reference information without compromising the uniqueness of the Symbol as a key value.

    Finally, you can always compare the result of calling .toString() on two similarly-constructed Symbol instances. I suspect that that practice would be considered questionable, but you can certainly do it.

    edit more — it occurs to me that the fact that the default behavior of Symbol creation in JavaScript makes the type more useful than, say, atoms in Erlang or keys in Clojure. Because the language provides by default a value guaranteed to be unique, the fundamental problem of namespace collision is solved pretty nicely. It's still possible to work with "well-known" symbols, but having unique values available without having to worry about conflicts with other code that might also want to avoid collisions is nice. JavaScript has the somewhat unique, and certainly uniquely pervasive and uncontrollable, problem of a global namespace that may be polluted by code that a programmer does not even know exists, because code can collide in the browser enviroment due to the actions of a party different from, and unknown to, an arbitrary number of software architects.