Spider: An Exciting Alternative to JavaScript
Spider is one of the new languages that try to improve our codes by providing more reliability. Some could certainly describe it as CoffeeScript with JavaScript syntax, but such description would fail to emphasize the real benefits of Spider.
Spider contains a lot more unique and interesting concepts than most alternatives like CoffeeScript. While the latter is certainly more mature than Spider, we get some nice options by choosing the language named after the eight legged arthropods. If we just want to experiment a little bit with yet another language, search for a trustworthy JavaScript alternative, or try to write less and do more, Spider seems to be a good candidate.
Basic Concepts
Spider is designed around its slogan, It’s just JavaScript, but better.
This means we won’t get a compilation type system or type checker of any kind. We also won’t miss our beloved C-style syntax with curly brackets for blocks, round brackets for function calls, and square brackets for arrays. Finally we also don’t see a custom VM on top of JavaScript or anything else to break compatibility with existing JavaScript code. Yes, this is really JavaScript.
The creators of Spider realized that there is no point in debating static versus dynamic languages. Each one has their advantages and disadvantages. The reason for choosing the full dynamic side with Spider is simple: JavaScript is already dynamic and interacting with otherwise dynamic code gets a lot simpler when the language embraces a dynamic type system.
There are two more important things that should be mentioned here:
- Spider is compiled to JavaScript (i.e. transpiled)
- Some features are inspired from languages like Go, C#, and CoffeeScript
The files are not transpiled to older versions of JavaScript, but to the most recent standard ECMAScript 6. To guarantee support across most browsers, Spider uses Google’s Traceur to generate ECMAScript 5 compatible files. What this means is that Spider is already taking advantage of future improvements, with the current output being backward compatible.
Syntax
Spider includes the ::
operator to access the global scope. This prevents us from doing something stupid without realizing it. However, this also means we need to write a little bit more to access, for instance, the console
object. The statement below shows an example that uses the ::
operator:
::console.log("Hello world!");
A possible way around this is to use the use
statement. It allows us to reference a locally undeclared symbol.
use console;
console.log("Hello world!");
Spider provides certain macros that unlock some well-known global objects. Depending on the type of application you’re developing, these macros can be more or less useful. One example is the following:
use :browser;
console.log(document.title, window.screen);
The :browser
macro allows us to use objects such as document
, console
, window
, location
, and many more directly. A very helpful feature for DOM intensive applications.
Instead of keeping all the former logical operators, some have been replaced. For instance the equality and inequality operators (==
and !=
) play now the role of strict equality and strict inequality (===
and !==
in JavaScript). The “and” (&&
) and the “or” (||
) operators also transform the value and have been renamed to and
and or
respectively. Here is an example:
// x == true;
x = false or 5;
// x == true;
x = 5 and 4;
// x == false;
x = 1 == "1";
Now some of you will scream, stop reading this article, and also close the page. But wait… don’t leave so fast!
The logical-and and logical-or operators have also been abused for controlling flow and placing default values. While the former is not so interesting, the latter can be a real time saver. The language uses the null-coalescing operator ??
from C# to cover default values:
x = options.name ?? 'default name';
At this point we are ready to have a look at functions. Functions are what make JavaScript so interesting. Spider doesn’t take away anything, except a few characters:
var square = fn (x) {
return x * x;
};
Instead of writing function
, in Spider we can write fn
. This saves us from typing a few keystrokes while keeping the same structure. As in JavaScript we can use functions in function statements or in function expressions. Function statements are restricted to named functions, just like in JavaScript.
Additionally we can use the function arrow ->
as in Java lambda expressions (and similar to the arrow functions in JavaScript). The previous example could be expressed as follows:
var square = (x) -> x * x;
If you don’t write a block, the function will immediately return the provided expression. On the contrary, if you have a block of statements you need to use a return
statement for returning a value.
But the simple function arrow is not enough. As in the TypeScript language (and also in ECMAScript 6) we also have the fat arrow =>
operator. This one is a context preserving function arrow. If you want to learn more about the arrow functions in JavaScript, I suggest you to read the article Preparing for ECMAScript 6: New Function Syntax.
The following is an example of this operator in Spider:
fn Animal(name) {
this.name = name;
this.printNameLater = () => {
::setTimeout(() => {
::console.log(this.name);
}, 1000);
};
}
One additional remark for functions is the ability to specify default parameters and use the rest parameters like in ECMAScript 6. The former automatically generates code to check and fix missing (i.e. undefined
) arguments. The latter is similar to variable argument lists. It basically groups all additional, unnamed parameters into one named array:
fn format(text, parameters...) {
for parameter, index in parameters
text = text.replace('{' + index + '}', parameter);
return text;
}
format("Hi {0}! My name is {1}.", "World", "Florian");
In the previous example we’ve also seen one of Spider’s elegant ways to write a loop. We used a classic foreach
loop with an additional iteration counter. Spider also contains more such features, as we will see in the next section.
Features
Spider brings a lot more safety to JavaScript by introducing more consistency. An example for a more consistent approach can be found in the name of types.
// "object"
typeof { a: 4 };
// "array"
typeof [1, 2, 3];
// "date"
typeof new Date;
// "number"
typeof new Number(4);
// "string"
typeof new String("abc");
// "boolean"
typeof new Boolean(true);
As you can see the type for arrays and dates is different from JavaScript and it’s more close to what you (or most people) would expect. Another safety check can be found with the existential operator ?
. It transforms any expression to a check for null
or undefined
. This can be quite handy:
if game? {
play();
}
There are also other variants, namely ?.
(also called Elvis operator) for calling properties or ?()
for calling functions. Hence the following could make sense:
game?.play?();
Here we only access the play
property if game
is defined. If play
is not a function, then nothing is called.
Transpilation
I’ve already mentioned that Spider actually transpiles to ECMAScript 6. As a positive side effect, Spider is quite future proof and uses features of JavaScript that are accessible today. However, there is also a disadvantage in targeting ES6: we still need another transpiler to convert the output to ES5 or lower, which can be interpreted by all modern browsers (including older versions of Internet Explorer).
For the transpilation we need the Spider compiler. The best solution is to install the npm package spider-script:
npm install -g spider-script
This also installs Traceur, PEG.js and a bunch of other dependencies. The major drawback of using Traceur is an additional runtime dependency.
At this point we have access to the Spider compiler, which is called spider
. By default the compiler transpiles and runs the code in ES5 mode without hitting the disk. However, there are several options to change that behavior and write output files with optional source maps instead.
A Short Demo
Rather than staying on the theoretical side, I want you to practice a bit with Spider by creating a small demo application. Our goal is to use as many of the features of Spider as possible. Apart from that, the demo should also be fun to use, so we’ll create a simple game. By creating this project, you’ll also have a glance at Spider’s amazing inheritance features.
The Basic Concept
We’ll create a simple space shooter game where our ship is portrayed as a triangle and opponents are represented as circles. Any collision will result in an annihilation of the player. The game will be drawn by using an HTML5 canvas with a 2D drawing context.
We won’t focus on the graphics, as our attention and interested should be focused on the code. We’ll create a constructor function called GameObject()
, which will also be the prototype
of the constructor functions PlayerShip()
and Asteroid()
. An object game
will aggregate all the objects of the game.
To start you’ll need to download a few resources for our game. We require a nice background image and a sound to play in case of a collision. The game is controlled via the arrow keys of the keyboard.
Implementation in Spider
Every game requires a sort of resource loader. The demand is even higher if resources are loaded via a network. The following method encapsulates the process of loading an image from a given URL in a promise:
fn loadImage(url) {
return new Promise(fn (fulfill, reject) {
var img = document.createElement('img');
img.src = url;
img.onload = () -> {
fulfill(img);
};
img.onerror = () -> {
reject(img);
};
});
}
The interesting part is that we can simply use it in our startup routine, just as if we would deal with classic sequential code:
background.image = await loadImage('http://i.ytimg.com/vi/qbzFSfWwp-w/maxresdefault.jpg');
The background
object is a special kind of dummy game object. The constructor function uses a GameObject
as its prototype:
fn Background(game)
extends GameObject(game) {
this.draw = () => {
if this.image? {
var ctx = this.game.ctx;
var img = this.image;
var w = ctx.canvas.width;
var h = ctx.canvas.height;
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, -0.5 * w, -0.5 * h, w, h);
}
};
}
We do not need to specify the prototype
directly. We have to express our basic intent, which is to extend the GameObject
constructor function with a more specialized one.
The game also contains other pseudo objects. As an example we might have a generator for asteroids. Here features such as inline loops and ranges come in handy. We only want to create an asteroid at a random time and a random position.
Getting three random numbers (here called a
, b
and c
) can be done in a single line:
fn AsteroidGenerator(game)
extends GameObject(game) {
this.move = () => {
if Math.random() > 0.95 {
var [a, b, c] = [Math.random() for i in [1..3]];
// ...
game.items <- new Asteroid(game, location, velocity, radius);
}
};
}
Finally, we also will employ helpers such as a simple Point
constructor. As an example, we can always set a default value for any argument. This will reduce boilerplate code that only checks for undefined
and applies the default value:
fn Point(x = 0, y = 0) {
this.x = x;
this.y = y;
// ...
this.dist = (that) => {
return Math.sqrt(Math.pow(this.x - that.x, 2) + Math.pow(this.y - that.y, 2));
};
}
The finished demo application can be viewed at html5.florian-rappl.de/Spider/. The original source code is accessible via GitHub.
Key Observations
Let’s recap the features we’ve seen in action:
async
andawait
solve the callback hell- Prototype inheritance got simpler
- Shorthand method names make the code more elegant
- Ranges are great in many scenarios
- Default values are helpful
- The overall code is easier to read
In my opinion, reading Spider code does not feel strange or completely new to you. In fact, it turns out that this seems more like a natural extension to JavaScript than a new language.
Conclusion
Spider comes along with some handy new features and also brings some consistency to the table. It embraces the dynamic nature of JavaScript instead of fighting it. While other languages try to counter bugs by introducing compile-time features, Spider builds upon an improved language specification. A lot of bugs will definitely be gone for good.
In this article we’ve seen some of the unique selling points of Spider. We’ve also build a very small sample application that makes use of Spider’s new features. A lot more is possible with Spider than what I covered in this article. For this reason, I recommend you to check the official documentation available at spiderlang.org.
What’s your opinion on Spider? Does it have some appealing features or are you completely satisfied with your current workflow?