IronJS Blog

All things IronJS and F#

Archive for June 2011

My gripes with JavaScript

with 77 comments

Note: This post doesn’t mean I’ll stop working on IronJS, I love working on IronJS and will continue to do so to make it as fast and awesome as possible.

Apparently my recent appearance on hanselminutes caused some stir on twitter. People think I was laying to heavily into JavaScript as a feasible platform for server side development (node.js, etc.).

This might sound odd coming from someone that has built a JavaScript runtime, but my point of view after having developed IronJS is that there are a couple of critical problems with JavaScript that prevents it from ever being a viable alternative as development platform for server application development.

Lack of language defined modules and namespaces

The language doesn’t define the concept of a module or namespaces, several work-arounds exist like require in node.js but there are several problems with using something with require.

  • It’s implementation dependent, which means it’s not standardized and you can’t count on it existing in every javascript implementation.
  • You can re-write anything in any namespace at will, some people like this since it allows for something called monkey-patching. However this just leads to a whole new can of problems.
  • You can replace the require function, or any other function for that matter, at a whim or by mistake leading to very hard to track bugs.

ECMA specification doesn’t define how to organize code over several files

This ties into the previous modules and namespaces, but it’s such a major thing thing it has to be mentioned separately. The specification does not define how to load code from a file, this might seem like a minor issue as node “solved” it with require. But in reality the possible issues that can arise here are many.

For example, as already mentioned, require is specific to node.js and doesn’t exist on any other major platforms. There are major problems with having non-standardized and implementation specific behavior that effect such crucial parts of a language as loading code.

The most obvious one is how the code actually is loaded, for example:

require("foo");
require("foo");

Is the “foo” module/file loaded/executed once or twice?

There’s also more subtle problems, such as: When you use require to load a piece of code, is the code executed in the global environment or the loading context?

(function foo(bar) {
    require("foo");
}({x: 1}));

Is the code inside the “foo” module/file executed in the global scope or in the context of the foo function with “foo” and “bar” already bound? Most people will probably say “in the global scope”, but the designers of the PHP include probably won’t agree.

And before you mention CommonJS: It is what some people think is correct and does in no way represent some type of standard

Very small standard library

The standard library, as defined by the specification is incredibly small (compared to the Python standard library, or the .NET BCL, etc.) and only gives you access to the most basic operations, not even I/O is included. Sure you can build your own library for a specific implementation, but it’s not going to be universal and it will tie the code that relies on it to that specific implementation.

Language problems

If you’ve ever written any moderate amount of JavaScript code you’ll know that there are several problems with the language itself, things like with, eval or the inner quirks of how JavaScript applies the concept of equality. The http://wtfjs.com/ is a good read for some more WTF? moments.

Null vs. undefined

JavaScript has two “this is not here/doesn’t exist” values, they’re subtly different and undefined is the one that is most common. But why would you even have two different types of ‘nil’ to begin with? I know of no other language in existence that does this (I’m sure someone reading this is going to dig up another language that has this).

Context sensitive function keyword

In case you didn’t know, these two functions are not identical:

http://wtfjs.com/

(function bar() { })
function foo() { }

Finding out the difference I’ll leave as an exercise to the reader.

Limited set of data types

  • Only 8 byte float numbers, how would you even interface to a database schema that has a 64bit integer column?
  • No fast (as in native) arrays
  • No int, byte, etc. number types

Fragmented runtimes

While pretty much every single one of these problems could be fixed by either extending the specific runtime you’re using or by ECMA releasing a new standard, it’s just not that simple. Assuming you extend the runtime you’re using to allow for things like native/fast arrays and you define a module/namespace solution like node.js has done, now any code you write will only run on that specific runtime with your extensions which punches a big hole in the “same language everywhere”-argument that you hear every day on twitter (and it really is server-side JavaScript’s only claim to fame).

So what if ECMA releases a new standard that fixes every single problem I’ve listed above (this is no way an exhaustive list)? I can currently count to about six different runtimes in use today (JScript, JeagerMonkey, TraceMonkey, V8, Chakra, Carakan, Rhino). And that’s not even counting the small emerging ones like IronJS, Jurassic, Jint, etc. that are platform specific implementations for embedding. A lot of these runtimes are available in different versions in different browsers and will never be upgraded, so you’ll have to exclude all those new features in our new utopia-style ECMA specification if you wan’t to be “cross-runtime” compatible (which you need for web development at least).

But what about node.js?

First I want to make clear that IronJS is in no way a competitor to node.js, IronJS is a runtime – node.js is an application server that uses another runtime (V8).

Node is decently fast, sure. But it’s nowhere near as breathtakingly fast as it’s zealots want you to believe. Nor are any of the ideas it employs new or groundbreaking. It also comes with several warts that have been inherited from JavaScript which forces you to do manual continuation-passing style. I just can’t see the reasoning behind using a language that wasn’t designed to be used in this context, it just feels like one big hack (albeit a pretty well preforming hack).

Assuming you want to run an asynchronous SQL query in node, it’d go something like this:

db.query("select * from person", function(result) {
    print(result);
});

Now compare this to a language that actually was designed (F#):

async {
    let! result = db.query "select * from person"
    print result
}

Conclusion

The way I see it is that JavaScript has dug itself into a hole that is impossible to get out of, at least if you wan’t to keep “same language/code everywhere” idea alive. And if you’re not, then why would you use JavaScript when so many better (and faster) alternatives exist? There are so many problems with the language, I’ll list a couple more that I haven’t touched on but I think this image illustrates my point of view better then any words could:

  • The scoping of the this keyword
  • Switch case fall through
  • Automatic semicolon insertion
  • Bitwise operators that work on doubles
  • Type wrappers and type conversions, new String(“foo”) vs. “foo”
  • The new keyword makes function behavior dependent on the context they’re called from
  • with and eval (it doesn’t hurt to mention these again)
  • The arguments object which is an array, almost. Except it’s not. And it’s also magically bound to the parameter variables
  • The typeof operator
  • The global object

Written by Fredrik Holmström

June 22, 2011 at 12:38 pm

Posted in IronJS