Please join me at my new location bryankyle.com

Wednesday, May 11, 2011

The JavaScript Curse

There are 2 fundamentally opposing approaches to programming language design: large and small. Large languages have lots of features, most of which will include some sort of syntax. As the language gets larger, so too does the syntax. This means that there can be a lot of memorization involved for the programmer. In extreme cases (C++) no one really uses the entire language. Instead everyone uses their own subset that they’re comfortable with. One nice thing about large languages is that they have codified a way of performing certain tasks. For example: there is a single way to define a class. There are only a handful of ways to loop over a list or branch.

Small languages by contrast are very spartan in terms of their syntax, but this by no means affects what the language is capable of. Small language designers tend to pull in very few, but few powerful features that can be combined in numerous ways. Since the language is so small everyone tends to use all of it. However, there is such a thing as too much power. While larger languages have a chosen and blessed way of performing certain fundamental tasks small languages do not. Instead each developer comes up with their own way of doing things. This is the crux of the Lisp Curse.

Lisp is so powerful that problems which are technical issues in other programming languages are social issues in Lisp.

I posit that this same curse affects JavaScript, however the penalty for this curse isn’t nearly as high in JavaScript as it is in Lisp. Before I explain why I think that, let me first try to convince you that JavaScript is also affected by the curse.

Classes vs Prototypes

JavaScript took a different approach when it came to designing its object system. Unlike most other object-oriented languages JavaScript uses prototypal inheritance instead of the more common class-based model. We could argue until we’re blue in the face about which is better or more expressive, but that’s not the point. Developers weren’t happy with this model. And so, since the language is extremely malleable and the effort required to implement class-based inheritance is minimal everyone and their dog rolled their own.

Now we have a fractured situation. Every JavaScript library has their own prescriptive way of doing inheritance. Learning a new library is no longer about learning an API, now you have to learn an object system and its library-specific syntax too. Granted, all of the different syntaxes are made of the common building blocks present in JavaScript, but how they’re assembled is generally quite different. The issue of how-do-I-write-a-class is now a social issue since how you do it depends on the library you or your company decides to work with.

Concurrency

JavaScript is a single threaded language. This means there is only ever a single thread of JavaScript code running at a time. Some might consider this a good thing, I know I do. It makes JavaScript programs easier to reason about since you don’t have to worry about dead locks, race conditions or much of the complexities of writing a multi-threaded program. This also means that it needs a strategy for performing long running operations “in the background.”

In the context of a web browser, a typical long running operation would be using the XMLHttpRequest object to make an HTTP request. Due to network conditions or response size the amount of time it takes to make a request and get a response varies. If this were done in a single thread as a blocking call the JavaScript thread would pause until the response was received. This in turn means that the browser’s UI thread would block as well. Effectively it would look to the user as if the browser had hung. The solution to this problem is to perform the request asynchronously (hence the first A in AJAX – Asynchronous JavaScript And XML).

Asynchronous requests work by registering a callback and performing the operation in another OS thread. Given the restrictions of the language this implies that the asynchronous call is done by a native component (XMLHttpRequest). When the operation is complete or something interesting happens the a call to the callback is issued. This more or less works well. But cracks start to appear when you need to chain asynchronous operations together or perform several operations concurrently and proceed when they all complete.

In my experience this is something that happens quite often when writing programs in node.js. At least, when you’re doing asynchronous I/O with node.js. The language itself gives you no help at all. Instead you need to either handle the complexity yourself or use a library that someone else built. While this is not a trivial as implementing classes, it’s a fairly well understood problem and a library can be written if fairly short order. As with classes, this issue which should be addressed at the language level ends up being addressed at the library level, which in turn becomes a social issue.

The Way Forward

Hopefully I’ve convinced you that JavaScript has at least a few problems. The language is extremely powerful in terms of its capabilities. But some of the fundamental building blocks that are needed or expected aren’t present. And due to the power of the language, developers can and go off and write the pieces that they’re missing.

This effort at the library level, while fun and impressive, is ultimately not productive. Every body either writes their own library or uses an existing one leading to fragmentation. Since there a hundreds of different libraries for doing essentially something that should have been provided at the language level, the language suffers.

This is similar to the Lisp Curse in that the power of the language enables developers to do this sort of thing. The language can be tailored to the needs of the developer or software under development. While this was a curse for Lisp which lead to its niche status, the same fate will not befall JavaScript.

There were several issues that lead to Lisp’s downfall, but the biggest one, aside from having too much power, was the ability for developers to switch platforms. Developers could retreat to another language that better suited their needs. They could do this because Lisp works at the machine level. Programs developed in Lisp are compiled directly into machine code. This effectively puts Lisp on the same footing as C, C++, and other native languages. With JavaScript this isn’t the case.

JavaScript is the native language of the web. To compare, writing in JavaScript is akin to writing in assembler. There isn’t a level below JavaScript that a web developer can write to. It goes without saying that JavaScript as a language is fairly stagnant. Sure, the mailing lists are quite active, and Mozilla is incorporating new features from the standards body. But ultimately there is a several year delay on the wide spread use of these language features. Older browsers that are still in use and supported don’t get these updates. As a result, developers tend to aim for the lower common denominator. In order to get a more modern language there is only one way to go: up.

Languages and tools have started to emerge over the past few years that allow developers to write code for the web in languages other than JavaScript. Google Web Toolkit, CoffeeScript, Objective-J are a few examples. These languages treat JavaScript as their target, much like a traditional compiler would treat assembler or machine code. But why are these new languages croping up instead of enhancements to JavaScript? I think it goes without saying

So while the Lisp Curse was fatal to Lisp, it is simply a nuisance that most developers have to deal with. For a few there are languages that compile down into JavaScript that can provide the missing features that we’ve all come to expect from a modern language. But, as I’ll discuss in a later post, these present their own issues.