Mercerenies   
Home Games Projects Languages Blog Fun Contact Portfolio
Javascript did Something Clever
Posted June 4, 2018

I just want to take a minute to acknowledge something that I think is quite clever in ECMAScript 6, and that's the way that the new Symbol type is used. But first, let's get the pedantry out of the way. If you're not feeling pedantic, feel free to skip this next paragraph.

Yes, I know that the title is technically not fully correct. For the purposes of this post, I'm talking about the 6th edition of the ECMAScript standard, published in 2015. So it's not entirely, pedantically correct to say "Javascript" did anything, as it was the people behind the ECMAScript standard who came up with and implemented the idea for the things I'll be talking about here. I understand all of that, and I willfully choose to ignore it all, because the current title is just easier to say than "ECMAScript did something clever". Got it? Great! Moving on.

So what I want to talk about is symbols. Prior to ES6, keys on an object were always strings. Even if you thought you were using a number to index a field in some object, internally it was being stringified. Don't believe me? If you have Node.js installed, try the following interactions.


> foo = {}
{}
> foo[1] = "Numerical slot"
'Numerical slot'
> foo[1]
'Numerical slot'
> foo["1"]
'Numerical slot'
> foo
{ '1': 'Numerical slot' }
      

So internally, the number is being converted to a string. I belabor this point to make it clear that strings used to be the only things that could be used as field keys.

But this is not so in ES6 and beyond. A new type has been defined, called Symbol. In addition to strings, symbols can now be used as field keys. The simplest way to make a symbol is to use the interning form: Symbol.for(...)


> foo = {}
{}
> foo[1] = "Numerical slot"
'Numerical slot'
> foo[Symbol.for('1')] = "Symbolic slot"
'Symbolic slot'
> foo
{ '1': 'Numerical slot' }
> foo[Symbol.for('1')]
'Symbolic slot'
      

So now we have something that's actually different. Symbol.for('1') is a new symbol that's distinct from the ordinary string "1". Also note that the symbolic field was not listed in the default stringification for the object.

In addition to providing interned symbols, we can also make new ones, simply by calling Symbol directly.


> sym = Symbol()
Symbol()
> sym == sym
true
> Symbol() == Symbol()
false
      

As expected on that last line, the two new symbols are distinct from one another. We can use these new "made-up" symbols as keys on objects if we want, too.


> sym = Symbol()
Symbol()
> foo = {}
{}
> foo[sym] = 10
10
> foo[sym]
10
      

While this particular feature may not be immediately intuitive or familiar to a lot of programmers, it's not new to the field. Common Lisp has had a similar construct for awhile, under the name gensym, a function which produces a brand new symbol.


[1]> (gensym)
#:G7193
[2]> (eq (gensym) (gensym))
NIL
[3]> (let ((foo (gensym))) (eq foo foo))
T
      

Okay, admittedly, that may look a bit terrifying to non-Lispers, but we basically did the same thing here that we just did in Javascript. gensym makes a new symbol. Two distinct gensym'd symbols compare unequal, but the same symbol always compares equal to itself.1

So we have symbols, and we have a way of making new, made-up symbols. Like I said, Lisp did all that 40 years ago. No, the truly clever thing is how Javascript uses these symbols.

To frame the problem, let's say we want to make some objects iterable. In a lot of languages, this is done by, say, implementing some Iterable interface and writing an iterator() method. Well, Javascript doesn't have interfaces, so we'll cut out the middle man and just have iterator().

The problem with this in Javascript is that people frequently do funny things with objects. Since objects are basically glorified dictionaries, people are often inclined to use them as dictionaries. Maybe I'm using an object to store all of the players in my new revolutionary MMO, and I use the player's last name as the key.


> foo
{ smith: ..., banks: ..., 'ward-sachs': ... }
      

This is all fine and good, but what happens when my good friend Johnny Iterator shows up to try out my game?


> foo
{ smith: ..., banks: ..., 'ward-sachs': ..., iterator: ... }
      

Uh-oh! Now my fancy databse is iterable, and my poor friend Johnny has been resigned to iterating over it. If we ever try to use for ... of on my object, it'll behave very strangely. This goes double if I already had a method named iterator which actually produced an iterator, and then Johnny came along and overwrote it with his last name.

So we can't use strings. If only we had some way of making up new symbols that nobody else was using... Well, that's exactly what ES6 does. There are several "special" symbols (the formal term is "well-known symbols", but I'm still having trouble taking that term seriously as an official notion), one of which is actually Symbol.iterator, the symbol used for iterators.

Now, rather than arbitrarily looking for an iterator field when we need to iterate, we can look specifically for Symbol.iterator, a made-up symbol. And Johnny's last name can't possibly be this symbol, since the Javascript runtime just now made up that symbol, and I'm pretty sure Johnny's last name was decided before the most recent execution of the Javascript runtime.

I don't really have a ton more to say. There are several well-known symbols defined in ES6. A full list can be found on MDN. I think it's a really clever solution to the problem of namespace pollution, and I'm considering implementing a similar technique for certain handlers in my own prototype-oriented programming language.


1
T and NIL are Common Lisp's way of saying, respectively, true and false. These values actually mean several things in Common Lisp and have some interesting philosophical implications, but that's a topic for another post.
[back to blog index]