It's also not really true: they're the opposite of symbols in lisp and ruby. In lisp and ruby, `:a == :a` is true, while in JavaScript `Symbol("a") == Symbol("a")` is false.
They are similar, but with kind of inverted defaults.
Symbol("a") == Symbol("a") is more-less equivalent to Common Lisp's (eq (make-symbol "a") (make-symbol "a")), both of which will return false, because (in Lisp terms) we're creating two different symbols. In Common Lisp, the default behaviour of a reader if it sees a stand-alone symbol token, is to intern it - put it in "global"[0] storage, so that if the reader sees the same-looking symbol token again, it returns the same symbol instance it previously interned.
In JavaScript, per this article, the interning behaviour is available through Symbol.for(), i.e. Symbol.for("a") == Symbol.for("a") should return true, much like (eq 'a 'a) would return true in Lisp.
--
[0] - it's actually per-package storage, but that's getting into the more advanced details of CL.
--
EDIT replaced gensym with make-symbol in Common Lisp example, as the former is used primarily in writing macros, so it ensures the created symbol has an unique printed representation, which helps make macroexpansions more readable.
(make-symbol "FOO") -> #:FOO
(make-symbol "FOO") -> #:FOO ; NOT eq to the previous one
(gensym "FOO") -> #:FOO103
(gensym "FOO") -> #:FOO104 ; note the incrementing counter appended by gensym
But in Ruby people use symbols to specify an object's properties, and they aren't used that way in JavaScript at all. In JavaScript they are used for metadata and other things you don't want to be serialized by JSON.stringify() or Object.assign(). I've always been confused by why Ruby uses symbols so much.
Well, Ruby objects are basically big hash tables in a way. Makes sense to use a prehashed string to index them (if I understand how symbols work correctly).
Using symbols I think is more expressive than using strings.
I like the separation of strings for external data and symbols for internal data.
So symbols will usually be used for enums or things which logic or control flow depend on. Symbols then have the benefit of feeling like syntax and the scheme zen of code is data is code...
> In JavaScript they are used for metadata and other things you don't want to be serialized by JSON.stringify() or Object.assign().
They're used to avoid conflicts for "generic method" hooks (similar to dunder methods in Python): the conflict issues is why environments have broken when new methods were added, and why e.g. Object.keys and their ilk are "static" functions rather than methods.
If you don't want JSON.stringify or Object.assign to see your properties just make them non-enumerable e.g.:
Object.assign will copy symbol-keyed properties. In fact Symbol-keyed properties are enumerable by default.
They live in an odd half-way world because many constructs only accept/handle string-keyed properties: Object.keys/entries/values and JSON.stringify use EnumerableOwnProperties and (for..in) uses EnumerateObjectProperties, both of which are specified to ignore non-string-keyed properties (before even checking for enumerability).
> I've always been confused by why Ruby uses symbols so much.
> I've always been confused by why Ruby uses symbols so much.
Good: Other people mainly covered it. Also note that when writing extensions using the C API, accepting options as symbols is generally much more convenient.
Less good:
val = "something"
val = :something
# Saves one character and no need for an end token.
{ "key" => val }
{ key: val }
# Saves four characters.
I'm designing a programming language that has symbols that are namespaced to objects, where two symbols are equal iff the object they're linked to is the same and their string representation is the same.
In that language, the equivalent of :a == :a would be true, and you would use different "namespace objects" to prevent collisions.
For example, classes that implement the function interface could provide methods named call and partial, that are namespaced to the function class object, leaving you free to define methods with those names but different namespaces for completely different purposes.
Why is that helpful? In JS, the way you accomplish the same thing is to assign the single instance of the symbol to a property in your module, and that's how you access it, rather than summoning it with the Symbol function. Not sure what the machinery you're suggesting buys you, but I could be missing something!
I don't suppose it buys you anything JS can't do, but it does allow those things to be integrated closely in the language, in a way JS can't do without majorly breaking the language.
> Hopefully there will still be useful cases for them.
There are already useful cases for them:
* non-conflicting extensions to existing prototypes, in fact most of the standard uses of symbol are exactly that, it finally allows the standard to add new methods to existing types (Object, String, Regex, …) without breaking everything and risking conflict with existing extensions
* similarly non-conflicting properties to non-prototype third-party objects for e.g. decorators and proxies and the like (sometimes you can't avoid bundling your data as part of the original payload)
* much like (gensym) though somewhat less convenient, can be used to avoid conflicts when doing codegen
* more convenient/debuggable sentinel object than e.g. `{}` which would tell you very little when you unwittingly leaked it, especially as symbols may be the only builtin object which is not implicitly convertible to a string
I haven't really dealt with this level of JS before, so unfortunately I'm ignorant of every one of your bullet points. Good to see it's got lots of uses though.
> I haven't really dealt with this level of JS before, so unfortunately I'm ignorant of every one of your bullet points.
That's not necessarily a bad thing, #1 is mostly of interest to the standard committee and historically considered a bad move (and in fact the source of the issue) for third parties (you may have to use its result if you want to e.g. make one of your objects iterable though), #2 is something you do when all options are exhausted, #3 is when you write your own codegen.
#4 is probably the one you're most likely to encounter, it's not that common either, and `null` or `undefined` generally work as sentinels so…
Oh sure, yeah I see how #4 would be useful. Generally I've used / seen `undefined` used for that in the past, which can be problematic because it might mean what you think it means, or it might mean something broken in your algo by mistake.
Is this someone's slide deck as article? Beginning with what symbols aren't — not logos, not emoji, not cymbals, har har — seems more suited for a cheesy in-person ice breaker.
24 comments
[ 3.1 ms ] story [ 64.0 ms ] threadThis is why I'm not massively interested in ES6 symbols tbh, though I could be missing something.
Hopefully there will still be useful cases for them.
Symbol("a") == Symbol("a") is more-less equivalent to Common Lisp's (eq (make-symbol "a") (make-symbol "a")), both of which will return false, because (in Lisp terms) we're creating two different symbols. In Common Lisp, the default behaviour of a reader if it sees a stand-alone symbol token, is to intern it - put it in "global"[0] storage, so that if the reader sees the same-looking symbol token again, it returns the same symbol instance it previously interned.
In JavaScript, per this article, the interning behaviour is available through Symbol.for(), i.e. Symbol.for("a") == Symbol.for("a") should return true, much like (eq 'a 'a) would return true in Lisp.
--
[0] - it's actually per-package storage, but that's getting into the more advanced details of CL.
--
EDIT replaced gensym with make-symbol in Common Lisp example, as the former is used primarily in writing macros, so it ensures the created symbol has an unique printed representation, which helps make macroexpansions more readable.
Compare http://clhs.lisp.se/Body/f_mk_sym.htm and http://clhs.lisp.se/Body/f_gensym.htm.Perhaps you meant Symbol.for("a") for both?
I like the separation of strings for external data and symbols for internal data.
So symbols will usually be used for enums or things which logic or control flow depend on. Symbols then have the benefit of feeling like syntax and the scheme zen of code is data is code...
They're used to avoid conflicts for "generic method" hooks (similar to dunder methods in Python): the conflict issues is why environments have broken when new methods were added, and why e.g. Object.keys and their ilk are "static" functions rather than methods.
If you don't want JSON.stringify or Object.assign to see your properties just make them non-enumerable e.g.:
Object.assign will copy symbol-keyed properties. In fact Symbol-keyed properties are enumerable by default.They live in an odd half-way world because many constructs only accept/handle string-keyed properties: Object.keys/entries/values and JSON.stringify use EnumerableOwnProperties and (for..in) uses EnumerateObjectProperties, both of which are specified to ignore non-string-keyed properties (before even checking for enumerability).
> I've always been confused by why Ruby uses symbols so much.
Mutable non-interned strings.
Good: Other people mainly covered it. Also note that when writing extensions using the C API, accepting options as symbols is generally much more convenient.
Less good:
But `Symbol.for("a") === Symbol.for("a")`, and Symbol("a") is very similar to (make-symbol "a").
In that language, the equivalent of :a == :a would be true, and you would use different "namespace objects" to prevent collisions.
For example, classes that implement the function interface could provide methods named call and partial, that are namespaced to the function class object, leaving you free to define methods with those names but different namespaces for completely different purposes.
There are already useful cases for them:
* non-conflicting extensions to existing prototypes, in fact most of the standard uses of symbol are exactly that, it finally allows the standard to add new methods to existing types (Object, String, Regex, …) without breaking everything and risking conflict with existing extensions
* similarly non-conflicting properties to non-prototype third-party objects for e.g. decorators and proxies and the like (sometimes you can't avoid bundling your data as part of the original payload)
* much like (gensym) though somewhat less convenient, can be used to avoid conflicts when doing codegen
* more convenient/debuggable sentinel object than e.g. `{}` which would tell you very little when you unwittingly leaked it, especially as symbols may be the only builtin object which is not implicitly convertible to a string
I haven't really dealt with this level of JS before, so unfortunately I'm ignorant of every one of your bullet points. Good to see it's got lots of uses though.
That's not necessarily a bad thing, #1 is mostly of interest to the standard committee and historically considered a bad move (and in fact the source of the issue) for third parties (you may have to use its result if you want to e.g. make one of your objects iterable though), #2 is something you do when all options are exhausted, #3 is when you write your own codegen.
#4 is probably the one you're most likely to encounter, it's not that common either, and `null` or `undefined` generally work as sentinels so…
In particular, hasInstance is apparently implemented by some browsers now:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...