

ES6 in Depth – Symbols - jansc
https://hacks.mozilla.org/2015/06/es6-in-depth-symbols/

======
nni
It is my understanding that symbols were going to be a way to have private
methods. This aspect of them - the reason they were going to be introduced in
the first place - was dropped, and so you are left with its current limited
form with a much narrower use-case.

From A Rossberg (he's also the guy behind SoundScript) in a March 2014
stackoverflow post ([http://stackoverflow.com/questions/21724326/why-bring-
symbol...](http://stackoverflow.com/questions/21724326/why-bring-symbols-to-
javascript/22280202#22280202)):

"Enabling private properties ... was indeed the original motivation for
introducing symbols into JavaScript. Unfortunately, however, they ended up
being severely downgraded, and not private after all.

They are now known as unique symbols, and their only use is to avoid name
clashes between properties....Whether that is strong enough a motivation to
add symbols to the language is debatable."

and from R. Waldron as part of this response
([https://esdiscuss.org/topic/proposal-about-private-
symbol](https://esdiscuss.org/topic/proposal-about-private-symbol)) to a
proposal about a private symbol (Dec 2014)

"Ultimately it was decided that Symbol is just a symbol and that "private"
things will be dealt with orthogonally (and at a later date)."

YMMV

~~~
braythwayt
You can still use them to make almost-completely-private methods and
properties, it's just the syntactic sugar that was dropped:

[http://raganwald.com/2015/06/04/classes-are-
expressions.html](http://raganwald.com/2015/06/04/classes-are-
expressions.html)

HN discussion:

[https://news.ycombinator.com/item?id=9660658](https://news.ycombinator.com/item?id=9660658)

------
lispm
> Calling Symbol() creates a new symbol, a value that’s not equal to any other
> value.

Lisp:

    
    
        CL-USER 1 > (eq (make-symbol "Foo") (make-symbol "Foo"))
        NIL
    

> Symbols aren’t exactly like anything else
    
    
        CL-USER 2 > (type-of (make-symbol "Foo"))
        SYMBOL
    

> Trying to concatenate a symbol with strings will result in a TypeError.
    
    
        CL-USER 3 > (concatenate 'string "abc" 'foo "def")
    
        Error: In a call to LENGTH: FOO (of type SYMBOL) is not of type SEQUENCE.
    
    

> There are three ways to obtain a symbol.

> Call Symbol()
    
    
        (make-symbol "FOO")
    

> Call Symbol.for(string)
    
    
        (find-symbol "FOO")
    

Other than that symbols in Common Lisp have a package, a value, a function and
a property list. Symbols can be interned in a package or not. So-called
Keyword symbols are in the package KEYWORD and have itself as the value.
:I-AM-A-KEYWORD evaluates to :I-AM-A-KEYWORD.

~~~
drudru11
That first example with 'eq is a little surprising to me. Previous to your
post, I believed that Common Lisp symbols were equivalent to Lisp atoms. That
is, I just thought of symbols as a way of interning strings (like Smalltalk,
Objc, Ruby, etc.) ES6 Symbols reminded me of gensym. I didn't expect to see
ES6 having the same behaviour as Common Lisp!

Thanks for posting.

------
unwind
As a very green JS programmer, I found this post informative and fun.

I think this sentence has an editing mistake in it:

    
    
        It’s simply a property whose name is not a symbol rather than a string. 
    

I think that the "not" shouldn't be there, the double negation makes the
sentence fail to parse for me at least (ObCaveat: not a native speaker).

------
yoshuaw
I wrote a lil' article about using Symbols a month ago -
[https://medium.com/code-ops/party-tricks-with-es6-symbols-
ee...](https://medium.com/code-ops/party-tricks-with-es6-symbols-ee328fdb6c4b)

Probably the most interesting use I've found for Symbols so far is to detect
if an object / function was created by a specific factory -
[https://gist.github.com/yoshuawuyts/2bf8d5394e6f995791a0](https://gist.github.com/yoshuawuyts/2bf8d5394e6f995791a0)

------
BinaryIdiot
Symbols are odd. Maybe it's just me but they feel like they work "funny" in
JavaScript.

As the article says you can't implicitly convert a symbol's description to
string. Symbol is now the only native object in JavaScript that has this
behavior.

var str = "something" \+ "str"; // Works

var num = "something" \+ 5; // Works

var func = "something" \+ function () { }; // Works

var obj = "something" \+ { some: "test" }; // Works

var bool = "something" \+ true; // Works

var dt = "something" \+ Date.now(); // Works

var und = "something" \+ undefined; // Works

var nul = "something" \+ null; // Works

var nan = "something" \+ NaN; // Works

var sym = "something" \+ Symbol("test"); // Throws TypeError

Another thing, which is more of a style thing in my opinion, is you can
actually define a property with a Symbol which just seems awkward to me. I
mean sure you can use it as a property by design so why wouldn't you be able
to use defineProperty? I always felt those should be public types of
properties where you can add additional logic where necessary.

var obj = { };

Object.defineProperty(obj, Symbol("MyProp"), {

    
    
        get: function () { return 15; } 
    

});

I feel like almost the same thing with Symbol could be accomplished with a
simple UUID generator. It's a neat little thing it just feels awkward to me
with how JavaScript works.

~~~
jkrems
The other funny thing is: ES6 introduced `Map`, `Set`, and the `class`
keyword. All of them throw if you try to create a new instance without `new`.
It also introduced `Symbol`, which throws if you _do_ use `new`.

------
skrebbel
I don't understand why the author compares ES6 symbols to Ruby symbols;
they're _nothing_ alike. It's kind of the opposite: Ruby symbols are
guaranteed to be the same object if they have the same name, ES6 symbols are
guaranteed to be a _different_ object, even if they have the same name
(description)

~~~
espadrine

      s1 = Symbol.for('cat')
      s2 = Symbol.for('cat')
      s1 === s2  // true
    

The most common use-case for them is to generate symbols that don't collide,
though.

~~~
ahoge
`Symbol.for` uses a global registry and the whole thing is "stringly typed".

Looks like an anti-pattern to me.

If you want one particular symbol from some library, you could import it.

~~~
artursapek
This was the one thing I didn't understand. Why is using a global Symbol
registry any better than just using string properties like usual?

~~~
towelguy

        s1 = Symbol("cat");
        s2 = Symbol.for("cat");
        s3 = Symbol.for("cat");
        s1 == s2; // false
        s2 == s3; // true
    

You can still have Symbols outside the registry.

~~~
artursapek
That I understand. What I don't understand is why s2 and s3 shouldn't just be
vanilla strings.

------
meric
Looks like Symbols is a type being introduced because objects, when used as a
key in another object, gets turned into a string, unlike Lua tables can be
used as keys of other tables, and are guaranteed to not be equal to any other
value. Lua tables are built-in symbols. Javascript's objects are not.

~~~
bcoates
Yeah, it seems like kind of an odd choice to introduce a whole new type _and_
extend the kind of values that can index an object, when extending object
indexing alone would do everything symbols do anyway.

~~~
quarterto
It's actually not extending object indexing. From the object's point of view,
the symbol is just another (albeit opaque) string.

~~~
bcoates
There's still some magic involving the symbol properties being automatically
non-enumerable, right?

------
IshKebab
I wish javascript had some kind of flag so you could get it to act sanely. All
the comparison operators would work like they do in sane languages; type
conversion wouldn't be _quite_ so automatic and insane, etc.

~~~
jordanwallwork
If you just make sure to always use === then this is exactly what happens. You
can use tools like jslint to warn you if you accidentally use ==

~~~
mkolosick
Not quite though. I had a bug with Javascript where I was reading in a number
and forgot to parse it to a float. I ended up doing an addition with that
value, which later got used as a float again. So I had 1 + "10" turn to 110
when I tried to use it. No == or === anywhere.

~~~
jordanwallwork
I was only really referring to automatic type conversion for comparisons. That
said, the example you gave sounds like the opposite, where you wanted it to do
some auto-conversion and you're disappointed that it didn't? If I did 1 + "10"
I don't think I'd want it to return 11!

~~~
afandian
I'd want it to throw some kind of cast exception, personally, not try and do
what it thinks I want. (I'm not the GP)

------
nabla9
This is very limited symbol type. They might have named them keywords instead.
It's just interned string with a new type.

(It's like Common Lisp symbols limited inside the keyword package)

------
welfare
I'm not entirely convinced with the function-call syntax for declaring a
Symbol.

Why not using something more of the lines with Ruby's :symbol syntax?

~~~
carussell
Here's the other thing that neither of the other commenters mentioned:

You can't go around peppering your code with use of this new syntax if you
want it to continue working on older runtimes. With `Symbol`, you can at least
create a polyfill that approximates what it's shooting for.

> declaring a Symbol

Don't think of it as "declaring a symbol" (because it isn't). Think of it as
_generating_ a symbol (because it is).

~~~
hajile
ES6 already adds a ton of new constructs that won't run on older browsers.
Relying on symbols to be interned also won't work on older machines. A symbol-
literal syntax like #symbol and #"spaced symbol" would make them far easier to
use and increase their adoption.

Who really wants to write map.add(new Symbol("foo"), 'bar') instead of
map.add(#foo, 'bar')?

~~~
ajuc
> Who really wants to write map.add(new Symbol("foo"), 'bar') instead of
> map.add(#foo, 'bar')?

It makes it clear when you are generating new symbol, and when you are reusing
existing symbol.

    
    
        var a = new Symbol("foo");
        var b = new Symbol("foo");
        map[a]="firstValue";
        map[b]="secondValue";
        Console.log(map[a] + " " + map[b]);
        // writes "firstValue secondValue"
    

How does it work with your syntax?

    
    
        map[#foo]="firstValue";
        map[#foo]="secondValue";
        Console.log(map[#foo]); // what is written here?

------
asdf99
yet another duct tape kind of feature, from people already too corrupted by
js, that will be further abused and cause yet more duct tape features in ES7.

this is nothing but a more convoluted way of the pattern that sets a unique
object as a unique value for comparison. well, it adds a label for easy debug.
hooray.

~~~
pmontra
Symbols are used all the time in Ruby (and a bunch of other languages), not as
duct tape but as a very core feature of the language. Why should it be
different in JavaScript?

The most simple way to use them is to replace definitions like this one

    
    
        var north = 1;
        var south = 2;
        var east = 3;
        var west = 4;
        var direction1 = north;
        var direction2 = south;
        direction1 === direction2 ? "ops" : "ok";
    

The programmer is doing the work of the interprer/compiler here and make sure
to pick unique values for all the constants that are going to be compared
together. With symbols that becomes

    
    
        var north = new Symbol();
        var south = new Symbol();
        var east = new Symbol();
        var west = new Symbol();
        var direction1 = north;
        var direction2 = south;
        direction1 === direction2 ? "ops" : "ok";
    

which is an improvement even if it is (in a traditional JavaScript way) so
much more verbose than Ruby's

    
    
        direction1 = :north
        direction2 = :south
        direction1 === direction2 ? "ops" : "ok"
    

I just wish they'll add some syntactical sugar to do without that "new
Symbol()" thing and create symbols as needed like Ruby does.

Unfortunately, from
[https://developer.mozilla.org/en/docs/Web/JavaScript/Referen...](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Symbol)

    
    
        Symbols and JSON.stringify()
    
        Symbol-keyed properties will be completely ignored when using JSON.stringify():
    
        JSON.stringify({[Symbol("foo")]: "foo"});                 
        // '{}'
    

We're going to manually serialize them when they leave the RAM.

~~~
ahoge
N, E, S, W? I think you want an enum for that.

~~~
pmontra
It was an example of replacing constants with symbols when the value of the
constant really doesn't matter. Purposely not fancy stuff. I understand the
advantages of enums (among the others, their values are constrained) but does
JS have enums?

~~~
ahoge
> _does JS have enums?_

No, but TypeScript and Dart do.

Enums don't really work without types.

But using Symbols for fake enums is probably a good idea.

    
    
        class Enum {
          constructor(...props) {
            props.forEach(p => this[p] = Symbol(p));
          }
        }
        const dir = Object.freeze(new Enum(...'NESW'));
        console.log(dir.E === dir.E); // true
        console.log(dir.E === dir.N); // false
    

Slightly awkward, but this might be about as good as it gets.

~~~
ahoge
I should have used Object.freeze(this) in the c'tor.

------
agd
_Call Symbol.for(string). This accesses a set of existing symbols called the
symbol registry. Unlike the unique symbols defined by Symbol(), symbols in the
symbol registry are shared. If you call Symbol.for( "cat") thirty times, it
will return the same symbol each time. The registry is useful when multiple
web pages, or multiple modules within the same web page, need to share a
symbol._

Have they added inbuilt support for globals here? People could easily add
their own global symbols, why does this need to be built in?

~~~
carussell
Could you reword the question? I don't understand what you're asking.

~~~
agd
I guess I don't understand why the global symbol registry exists. If someone
wants a global symbol map, they can create their own. What else does this add
over a user implementation, and what are the use cases?

~~~
pavlov
Making the registry global allows compilers to optimize away those
Symbol.for() calls, since they know that each call to Symbol.for("cat") is
going to resolve to the same symbol instance.

~~~
agd
But if people are concerned about performance, they can just pre-
allocate/memoize the symbols.

It doesn't seem like the registry adds any new functionality, and it might
encourage use of globals. Surely that's a bad thing?

------
jordanwallwork
I'm not from a Ruby/Lisp background, so I don't know how symbols are typically
used, but the explanation of why symbols are required is to prevent collisions
if two libraries chose to use the same property name; so given that, what is
the point of the Symbol registry? I don't understand what the value of a
symbol that is referenced by a string is, over simply using the string?

~~~
carussell
See `Symbol.hasInstance` and `Symbol.iterator`; take them as examples. Mapping
an object to its iterator is going to be part of the spec and will be required
at the language level (`for...of`). Before, if you looked at all of an
objects' properties in SpiderMonkey, you will have noticed an `@@iterator`
property. The spec could achieve the same thing as it's trying for with
`Symbol.iterator` by just using a string-valued `@@iterator` property, but you
still get the collision with anybody who would have used that name for
something else or anyone looping over all the properties.

The gist is this: symbols are just a way to have non-strings that you can use
for properties. There are _also_ facilities to generate them in such a way
that the symbol will never, ever collide with those generated by somebody
else, but that's not their only purpose.

~~~
jordanwallwork
Ah yeah that makes sense, cheers!

------
espadrine
If you think that this language feature has no equivalent in your favorite
language, it is similar to Ruby's :symbol, where it is mostly used as keys to
their hash tables (they're implemented with hashes internally).

Common Lisp has something even closer for its macro system. When you write
code that writes code, you often need to create new variable names that won't
collide or shadow anything. `(gensym)` does that:
[http://www.lispworks.com/documentation/lw50/CLHS/Body/f_gens...](http://www.lispworks.com/documentation/lw50/CLHS/Body/f_gensym.htm#gensym).

Here's a list of languages with support for symbols. You will see even
Objective-C has it:
[https://en.wikipedia.org/wiki/Symbol_%28programming%29#Suppo...](https://en.wikipedia.org/wiki/Symbol_%28programming%29#Support).

I'd be curious to learn how it is implemented in JS engines.

~~~
inglor
Like a property basically - except the key is not a string and is known to the
engine. This is both simple and uses existing JIT facilities. Symbol property
access isn't particularly slower than regular access this way too.

See
[http://blog.peschla.net/doxygen/v8_chromium_r157275/classv8_...](http://blog.peschla.net/doxygen/v8_chromium_r157275/classv8_1_1internal_1_1_symbol_key.html)
\- it's

------
xienze
> Sometimes it would be awfully convenient to stash some extra data on a
> JavaScript object that really belongs to someone else.

Convenient yes, a good idea, no.

> Other code using for-in or Object.keys() may stumble over the property you
> created.

> The standard committee may decide to add an .isMoving() method to all
> elements. Then you’re really hosed!

So I dunno, maybe don't stash properties into an object that doesn't belong to
you? It's this sort of thing that makes me hate the culture around JavaScript.
Hacks upon hacks upon hacks just to save a little effort.

~~~
sombremesa
I don't see why they didn't just use an Object/dictionary. Shouldn't be slow
to iterate and solves this exact use case.

