What I Think CoffeeScript Should Have Been
John K. Paul: What coffeescript should have been http://t.co/ib202q8M9b Damn, and the copy of CS in Action that I ordered 2 years ago was just finalized.
My father was a recovering alcoholic, who was sober for 30+ years before his death this past fall. I don’t remember a time before he was sober, but I do remember going to AA meetings with him when I was a little boy. Those meetings were often opened and closed with the Serenity Prayer. Over the years I have found this simple prayer to hold a great deal of life wisdom for dealing not only with addiction, but most of life’s challenges.
God, grant me the serenity to accept the things I cannot change,
The courage to change the things I can,
And wisdom to know the difference.
It might seem trite or trivial to do so, but it can be applied to the JavaScript minefield. The reality is, JavaScript is the language of the web. That is unlikely to change any time soon (despite Google’s hopes to the contrary with Dart). Furthermore, JavaScript is littered with land mines. This puts developers in a difficult predicament. We want to develop great software for the web, but we also want to have great tools. How can we change this situation to make it better? We can’t entirely and we will need the serenity to accept what we cannot change about the situation. Despite that, there are ways to address some of the problems with improved tools and languages. That path has been opened to us by the example of TypeScript, Dart and CoffeeScript, but it takes real courage to imagine an alternative to the status quo and even more to work to change it. More important than either serenity or courage, is wisdom to know when to apply each. Without that we easily go astray trying to change the unchangeable, or timidly fall short of the mark.
I’ve worked in JavaScript for a number of years now in my professional career, and I have to admit that I have never particularly enjoyed the experience. I’ve also written a small JavaScript library for input masking where unit testing was critical to ensuring correct behaviour. In that experience I got to really dig into the JavaScript object model. Through all that, I have felt that there must be a better way. All tools and languages have shortcomings and are better suited to certain tasks than others. However, compared to languages like C#, Java and C++ that I have worked in or languages like Scheme and Haskell that I have studied, JavaScript has more than its share of problems. If I had the power to design a language from scratch to be the language of the web that ran in all browsers, I would probably design some kind of statically typed multi-paradigm language combining the best of functional and object-oriented approaches. Unfortunately, we have inherited JavaScript and I recognize that is something outside my power to change. It is, however, possible that we could create an open source language compiled to JavaScript that could dramatically improve on it. I know many are attempting to do just that with varying degrees of success. I believe that no one has yet hit upon the right mix of features and syntax for such a language, and the language that will come to be seen as the successor to JavaScript has yet to be created. Based on my experience, research and ascetics I humbly propose 4 guiding principles or philosophies for such a project.
- It’s Just JavaScript, but Better
- Syntax Matters
- Embrace Prototypes
- Interoperate, don’t Imitate
Let’s explore each and see how they might be embodied in some proposed syntax. Please note that all code examples are just one possible syntax out of many.
It’s Just JavaScript, but Better
The example of CoffeeScript has clearly shown the advantages of an “It’s Just JavaScript” approach. A clear and direct mapping between the language and JavaScript makes it easy for the developer to understand what is happening and what they can expect. It aids early adoption when support for source maps may not be available yet. It provides a clear path to JavaScript interoperability. Importantly, this approach greatly simplifies the problem for an Open Source project that doesn’t have the resources to tackle a whole new platform approach the way Google has done with Dart. However, being just JavaScript doesn’t preclude deviations in semantics, not just syntax. CoffeeScript often shows an unwillingness to change JavaScript’s semantics even when it would be easy to do so and provide significant improvements to the developer. Instead we should strive to improve on JavaScript semantics when possible. That’s why, rather than “It’s Just JavaScript” or even “It’s Just JavaScript, the Good Parts” I propose “It’s Just JavaScript, but Better”.
Proposed Syntax
In syntax, “boring” is good. Deviating from syntax that is widely known without a good reason will probably just confuse people and hurt language adoption. I propose a largely C-style syntax with proper variable scoping. That is to say, you can’t use a variable or function before it is declared or outside the scope it is declared in. C# has carefully considered a lot of the issues and won’t, for example, allow one to shadow a variable or parameter in a method with another in the method. Scoping should just work, not cause any surprises and protect against unintended behaviour. Semicolons should be required statement terminators and white-space shouldn’t be significant. Parenthesis, commas and curly braces are required in the same places languages like Java, C# and JavaScript require them. All of that creates a language that is clear, unambiguous, easy to read and fast to compile.
To improve on JavaScript, each operator should have a single result type regardless of the types passed to it. This rule is followed in other languages with implicit conversions like VB.NET. Although the flexibility of JavaScript’s boolean operator leads to cute idioms like using ||
for null coalescing, it does nothing for readability or bug prevention. Boolean operators should always result in a boolean value.
To increase safety, all code should compile to JavaScript strict mode and be wrapped in an IIFE. Additionally, all uses of global scope should be explicit.
Syntax Matters
The words you use for something matter. Words and syntax can either aid in comprehension and remind us of how things work, or can obscure meaning and consistently mislead. Having syntax for something can change how you think about it, how often you use it and ultimately how you program. The first time I remember being truly struck by that was when anonymous inner classes were added to Java. For those not familiar with Java, that feature allows one to create an unnamed “class” inside a method and instantiate an instance of it that has access to the containing scope. It is very much like being able to declare an object literal inside a function in JavaScript and have access to the function closure. In Java, there are enough restrictions on anonymous classes to make for a straight forward translation to Java without that feature. In fact, that is how the compiler implements it. Nevertheless, this feature provided syntax for something and changed how most people wrote Java code even though it didn’t strictly add any capabilities. It was always possible to do before, but was so awkward that it was rarely done. For JavaScript the situation is a little different. With JavaScript there is certain semantics that we will not be able to change without too great a performance or complexity penalty. In those cases, we need to come up with syntax that clarifies the semantics of JavaScript.
Proposed Syntax
I have repeatedly raised the issue of the confusing semantics of this
in JavaScript. Attempts to improve JavaScript have generally made no attempt to address this issue. One of the two pillars of my approach is a new syntax for this
. In other object oriented languages, this
always refers to the current instance of the class lexically containing the occurrence of this
. In JavaScript, it actually has a significantly different meaning, but the value of this
ends up being equal to what it would have been given the more common definition in many situations. That is the root of the confusion. In order to create a syntax that clarifies this, we must first ask what are the semantics of this
in JavaScript. The value of this
can be determined in four different ways. Namely, through invoking a function after the .
operator, invoking the function with a value for this
using call
or apply
, or by invoking it as a constructor function using the new
operator. Additionally, it can be fixed to a particular value by calling bind
. The value of this could be better termed the context of the function call. A further confusion arises when functions are nested and have different contexts. In that situation, it is far too easy to accidentally reference the wrong context with this
, because one forgets that the context of the nested function is different from the context of the outer function. This mistake is so frequently made because the more common definition of this
is lexically based and the nested function is lexically nested, so it seems it should have the same context. I propose that both of these issues can be clarified by making the context a special named parameter of the function. Perhaps listed before the other parameters and separated from the rest by a dot rather than a comma, since the most common way of establishing the context of a function is with the dot operator.
You see above that I have also shortened the verbose function syntax to simply ->
. The arrow is widely recognized as a function operator and is a proposed addition to ES6. I propose that it be the only function declaration syntax. Additionally, the return line shows that the parenthesis around the argument can be omitted when there is a single argument and the body can be shortened when the function returns a single expression. This functionality is like lambda expressions in C#. So the return line is equivalent to return (cssClass) -> { return item.hasClass(cssClass); };
. Notice how there is no ambiguity over the context in the inner function because the context of the outer function is clearly and uniquely named. The typical mistake in JavaScript would be to write that line as return function(cssClass) { return this.hasClass(cssClass); };
, but that would be incorrect because this
does not refer to the context of the outer function inside the inner function.
A fully defined JavaScript alternative would probably have many other niceties like splats, array slicing, string interpolation, date literals, modules, default parameters, a call operator (perhaps ..
) and some kind of asynchronous programming support like C#, but built on top of promises and callbacks.
Embrace Prototypes
JavaScript doesn’t have classes. Instead it has object prototypes and constructor functions. Yet, many developers yearn for classes and try to extend JavaScript with class like functionality. However each implementation of classes in JavaScript is different and many are incompatible with each other. JavaScript developers can’t even agree on whether/how to use the new
keyword and declare constructors. As many authors, including Douglas Crockford, have noted, it is much simpler to embrace the prototype based nature of JavaScript and avoid not only classes but constructor functions by using Object.create(...)
to directly setup prototype chains. David Walsh has a great three part write up on this (part 1, 2, 3). By accepting this simple truth and embracing it we can greatly simplify both the semantics and syntax of our language. Following the principle that syntax matters we need a syntax that clarifies this. We can further simplify our language if we accept that JavaScript doesn’t have private properties (at least without introducing a reasonable amount of overhead).
Proposed Syntax
I think the syntax for objects is important and while I propose a concrete syntax here, I think there is further room for improvement. A syntax for prototypical inheritance is the second pillar of my approach. The essential idea is that all object construction is through object literals, we simply need a way to specify the prototype object when declaring an object literal. Additionally, our syntax can be much clearer if object literals have a clearly distinct syntax from function bodies (this is needed to support the lambda expression style syntax described before).
Note how using the =
operator in object declarations increases consistency across the language. Unlike in CoffeeScript, JavaScript property attributes (introduced in ES5) should be considered.
Support for property attributes will have to be carefully planned. Additionally, it needs to be considered how private properties would be supported if they were added to JavaScript.
There are some other possible syntaxes for object literals. I welcome suggestions for this. Keep in mind that it has to be distinct enough that the compiler and programmer can distinguish it. The syntax can’t be ambiguous with existing operators or when used with the lambda form for ->
described above (i.e. there must be something more than just {
to start the object). It should also imply the creation of an object with a prototype (which is not the same as inheritance). Finally, it is worth considering how difficult it is to type including on international keyboards (many language avoid $ for that reason). I’ve come up with a few more considering those criteria.
Interoperate, don’t Imitate
The last principle that I propose guides how this new language should interoperate with JavaScript and whether it needs to be powerful enough to express every valid JavaScript program. I think interoperability with all JavaScript features is critical. We simply can’t predict how libraries will make use of JavaScript features and must make sure that they can’t break the user experience of our language. For example, I think it is very bad that CoffeeScript assumes property access is side effect free and idempotent (returns the same value repeatedly). Those are simply not true if the property is actually a getter. While no code written in CoffeeScript will have getters, libraries will increasingly use them as a larger percentage of installed browsers support them. Decisions like that could easily be the downfall of a language. What if the next great library, the next “jQuery” relies on them heavily? That could be the end of CoffeeScript, or at least a very painful transition for CoffeeScript users as the problem was fixed.
While interoperability is critical, that doesn’t mean it must be necessary to fully use every language feature. I have already proposed abandoning the ability to directly use the new
operator or to easily declare constructor functions. It may be the case that not every property attribute or operator is important to have.
Proposed Syntax
While there should be no new
operator. It would be possible to invoke constructor functions declared in other libraries by using them in place of object prototypes.
Another way the language can “Interoperate, not Imitate”, is to provide functionality that is similar to but not equivalent to JavaScript. For example, the typeof
operator should be improved to at least return something more reasonable for the null
value. Additionally, the instanceof
operator should be replaced with something that checks directly against the prototype chain rather than falsely implying that there is such a thing as an object type tied to a constructor function.
What Now?
Having laid out these principles and proposed some concrete syntax based on them, the question becomes where do we go from here? I am seriously considering whether it is worth the effort of creating another JavaScript alternative. I have always been interested in programming languages and have the experience necessary to implement a compiler. However, I also know what a large commitment a project like that would be. Were I to start such a project, I would probably try to have an open dialogue process where people could provide input into language features and syntax and reasons for design decisions could be documented.
Gauging interest in another JavaScript alternative has been a major motivation in writing this series of articles. To that end, please take a moment to answer the question below. Furthermore, if you would be interested in contributing to such a language in any capacity whether it be coding, managing or design input please email me.
- Part 1 –
- Part 2 –
- Part 3 –
- Part 6 –
This Article