Designing a better Javascript

So for the past few years I've been toying around with the idea of writing a better Javascript than Javascript. The basic goals would be to implement the language in such a way that:

Now this might seem a bit overly optimistic, but when you remember that the idea is a better Javascript than Javascript, it frees us up to invent new ideas and reinterpret old ones. If the goal is to produce a better language, it would be counter productive to preserve all of the cruft. If one is going to go to the effort of writing a new compiler, you might as well fix the mistakes in the language while you're at it.

What do we keep?

So the first question to as is "what do we keep"? The answer to that is probably the simplest part of it all:

What do we get rid of?

Now the second question of "what do we get rid of?" is definitely the harder of the two from a political standpoint. How much is too much is the obvious concern. How alien a language can it become before it is unrecognizable as another Javascript derived language? So before we get too far down that line of thinking, let's hit some low hanging fruit:

Where do we go from here?

So if you read through the Phosphor code base, you'll notice that I occasionally go to some length to minimize the above concepts in the code. Nearly all of my functions return values. Many of the above concepts could be removed almost entirely from the code base, within the existing features of Javascript. Part of the problem one would face though is performance suffers as you focus on the good parts and exclude the fiddly bits. But this is because the implementations are all designed around those fiddly bits.

So what would a high level architecture look like for the new language:

With this high level conceptual framework in place, much of what is hard to do in Javascript becomes trivial, and many of the features that appear to require special forms, become simple method calls. Additionally, we can also eliminate many classes of conventional Javascript programming bugs, by making them impossible to write. Best of all, through a certain set of glasses, and a peculiar point of view, all of these statements can be considered true for Javascript as it stands today. But it really does take few giant mental leaps.

2 Stacks No Waiting

The idea of a dual stack architecture is not all that odd. Forth makes extensive use of the concept to allow for direct manipulation of the flow of a program. By adding or removing partial continuations from the Forth return stack, the programmer can have direct control over how the program will execute in the future. This concept also allows for incredibly powerful programming techniques which allows the user to produce their own flow control structures.

The main concept here involves giving the programmer more control over the future execution state of a program. To that end, we'll designate a few new special operators:

These 3 operators allow us to effectively manipulate the continuation stack. The -> operator will effectively zero the continuation stack, making the right hand side the top level of the program, allowing for conditional exits from deeply nested state. The ; operator is effectively no different from the ; punctuation in Javascript in practice, but it allows us to push the right hand side onto the continuation stack. The ^ operator is renamed from the rarely used xor operator (which we can rename to something else) and replaces the return keyword. Unlike the return keyword, however, we can double, triple, or more up the operator as a prefix, and return several levels up the continuation stack. As such ^ allows us to pop values off the continuation stack.

Everything's a Function

When you think of what a function is in practical terms, it is merely anything that maps one value to another. Objects map keys to values through a series of slots. Arrays map integers to values through an ordered collection. Strings map indexes to bytes. Numbers maps indexes to bits. And bits can be thought to map to 0 to 1 or 0, a simple one dimensional function in base 2 arithmetic. For our improved Javascript, we can embrace this concept and allow to be applied to anything. This allows us to hide the implementation details from the conceptual level. It also cleans up some of the API weirdness that afflicts the current implementation.

When combined with assignment, and a new range operator ... , this model allows us to do manipulate arbitrary bit fields, which is one of the current weaknesses of javascript. Once one has these mappings formalized, the list of valid transformations comes clearly into focus. By default, when a range is passed, the corresponding values are constructed in adjacent locations in memory. This allows for a piecemeal construction of bit fields as well. Since everything is a function, it is also possible to use () in place of , though indexing will be more legible in the long run.

Variables are Properties too

Currently in most Javascript implementations, global variables are actually just properties of the global object. But local variables are not necessarily slots in the local function object, rather they are more often slots in a scope object associated with the particular instance of that function object. Here we can fix a lot of the scoping problems, by simply stating that a variable is a message send to the current function. If the function fails to have such a property, it shall escalate it up the Object Stack, until it reaches the "top level" aka global object. So using this mental framework, the typical way that one would assign a value to a local function's property would be to declare it as a local variable using "var". But we can do that one better, and state that any token prefixed with a period . is automatically a property of the local function. Any token not prefixed with it is passed up the chain, and may or may not be a global.

The primary advantage of this approach is that it conforms well with the concept of an implicit recipient, and also allows for a more legible designation of what is local and what is not. And rather than write this.foo you can save 4 characters and simply write .foo instead. This also has advantages when nesting functions, and allows modules to define "globals" within their own scope, by making them "local" to the top level of the module. This prevents the old global variable scheme from stomping on your local variables due to cross contamination between code bases.

Punctuation as Operators

Javascript already has a very rich set of operators, and one of the major additions this design makes, is it formalizes the meaning of some punctuation as operators:

These additions to the core of the language simplify some of the common tasks Javascript programmers face. # and $ are repurposed to convert data types into specific forms. This avoids the need to use complex functions like parseInteger or toString methods to maintain sanity. The : operator formalizes the construction of slots, and allows for the creation of objects anywhere, even outside of notations, which simply designate any block of code. The , operator conjoins its left and right sides, with the product being dependent upon the types of values passed:

The new @ address of operator, allows for the construction of compositions of composite objects. When combined with the comma , operator concatenation takes on a distinct flair from addition. When and are used, the comma , operator treats them as if they were designating @ and @ such that , , is an array of 3 empty objects. @'hello', 'world' is an array of strings, whereas 'hello ' , 'world' is a composition of strings into 'hello world'.

Function Calls are Message Sends

The expression f(x) really sends a message f with object x up the Object Stack, until some object has a property f that can be evaluated for x. This concept is vital for building the next generation of networked software, as a script may operate in a scope that includes other computers. There is no good reason that an containing scope need be on the same CPU, or even on the same continent. As such, the message can be passed to any object within the scope. A backend process may be simply represented by an object whose scope our application runs. The removes entirely the need for AJAXianisms. It also makes Erlang look like a backwards bumpkin.

This concept of message send also means that many of the traditional flow control constructs are better modeled by sending different messages. When combined with the functional nature of all objects, decisions are better modeled through a combination of calculation and message sending,than they are special form conditional logic. This becomes doubly important when the scope of previous and future execution may occur on physically distinct hardware and systems.

Concluding Thoughts

If this system were implemented, the code would resemble Javascript in most ways, but it would have a distinct flavor. The learning curve necessary to learn this new language for conventional Javascript programmers would be slightly steep. Many of the old idioms simply are unnecessary or wrong, and would have to be unlearned. However, the typical Javascript programmer could learn to read the new code in a day. The resulting code would be denser, with fewer keywords, and much less prone to an over-reliance on inappropriate data modeling. It would also make writing distributed applications far more straight forward and natural.