CoffeeScript, Dart, Elm, and TypeScript
Script Mods
JavaScript is the stuff of which many interactive web clients is made, but it comes with a fair amount of historical ballast. The creators of four alternative scripting languages seek to ditch the ballast.
Browser-based interactive applications have helped the recent comeback of JavaScript, although the scripting language first saw the light of day in the mid-1990s. Over the course of time, JavaScript's makers added more and more new constructs to what was initially a fairly simple scripting language. One prime example is prototype-based object orientation, hated by many developers and often misunderstood and unused.
Not until 2015 was the scripting language, by then standardized as ECMAScript, given optional class-based object orientation. However, compared with Java, C++, and others, it still lacks certain features [1]; moreover, neither JavaScript nor ECMAScript offers type checking, which continually results in exception errors in practice.
For this reason, several scripting languages want to replace JavaScript – or at least simplify it in terms of programming. Some of the most widespread and popular examples are CoffeeScript, Google's Dart, Elm, and Microsoft's TypeScript. In a comparison, I describe what distinguishes the four candidates and in which areas they are superior to JavaScript.
CoffeeScript
Jeremy Ashkenas published the first version of CoffeeScript [2] in 2009. His scripting language does not simply reinvent the wheel; rather, it supplements JavaScript with additional constructs so that developers can continue to use existing JavaScript libraries such as jQuery [3]. When this issue went to press, CoffeeScript had reached version 1.12.4, which only supported ECMAScript (ES) 5 and some parts of ES2015. The upcoming CoffeeScript 2.0.0 will implement more elements from ES2015 – in particular, the classes it introduces.
The compiler is available under the MIT license and requires a JavaScript run-time environment (e.g., Node.js). CoffeeScript plugins exist for many text editors, such as Emacs and Gedit. If you want to try CoffeeScript first, you can do so from an online editor [4] on the project site (Figure 1).
A compiler provided by the CoffeeScript team translates CoffeeScript code into the JavaScript equivalent, which in turn runs in any browser. The JavaScript code is said to run faster than an equivalent written by hand.
CoffeeScript also lets you use shorthand notation. Among other things, you can leave out the var
before variable declarations and the semicolon at the end of an expression. Comments begin with a hashtag (#
), as in shell scripts, whereas two slashes (//
) cause integer division. Longer strings and regular expressions can run across multiple lines. CoffeeScript also lets you chop up arrays:
start = countdown[0..2]
In this case, start
only contains the first three items of countdown[]
. You can create functions with the ->
operator and set the parameters to default values:
mult = (x = 1) -> x * x
Rather than curly braces, indentations mark statement sections, as in Python. Listing 1 defines a Dot
object in this way with two properties and one method, draw()
. The splat operator (...
) lets you pass any number of parameters to a function. The ?
checks whether a variable exists.
Listing 1
Simple Object in CoffeeScript
The pattern <result> = <value> if <condition>
exists for simple conditions. Because the JavaScript comparison operator (==
) often leads to misinterpretations, the CoffeeScript compiler automatically translates it to ===
. Additionally, many Boolean and comparison operators have synonyms; for example, on
and off
are identical to true
and false
. Chained comparisons quickly test whether a variable lies in a particular range:
inpicture = 0 > Dot.x > 640
The scripting language also offers a Ruby-style switch
-case
construct.
Besides this, CoffeeScript extends for
loops to include comprehensions, which help the code iterate through the elements of an array in a particularly elegant way. At the same time, they are expressions and can be assigned and returned:
drive = (a) -> alert(a) drive auto for auto in ['bmw', 'vw', 'skoda'] when auto is not 'skoda'
The compiler also attempts to transform statements into expressions. Functions always return their last value, which means you can often omit return
. A while
loop also contains an array with the result of one iteration of a loop. In the example, countdown
contains an array with the numbers 9
to
:
counter = 10 countdown = while counter > 0 counter = counter - 1
CoffeeScript 1.12.4 introduced a simple class concept (Listing 2). Here, @
is the short form of this
; super
(line 8) invokes the function currently running in the parent class. The upcoming CoffeeScript 2.0.0 compiles the classes to create their counterparts from ES2015.
Listing 2
CoffeeScript Class Definition
CoffeeScript 1.12.4 already supported generator functions and tagged template literals from ES2015. Template literals incorporate the contents of variables into text:
greeting = "Hello #{name}"
In addition to the compiler, the CoffeeScript package includes a simple build system that works much like Make or Rake. Its core is a Cakefile
, in which you define the various tasks for the compiler. These can then be executed using the cake
tool. You then feed a markdown document with the .litcoffee
extension to the CoffeeScript compiler. The compiler interprets all the indented blocks in the document as CoffeeScript code and ignores the rest.
Dart
Google first presented its Dart [5] scripting language to the general public in 2011. Either a Dart virtual machine (VM) executes programs written in Dart, or a compiler converts them into JavaScript code. The Dart SDK provided by Google includes the Dart VM and compiler; it is available under a BSD-style license and includes other useful tools. For example, dartanalyzer
analyzes the Dart scripts fed to it and points out potential pitfalls. Additionally, a Chromium browser comes with an integrated Dart VM [6] dubbed Dartium.
Plugins help IDEs and editors such as Atom [7] and Emacs [8] learn the scripting language; WebStorm [9] even supports Dart out of the box. If you do not want to install the SDK, you can get started with the DartPad [10] online editor shown in Figure 2. According to Google, Dart is especially suited for programming larger applications. ECMA has now standardized Dart [11].
Each Dart program must have a main()
function as its entry point. Figure 2 on the left shows an example of a simple script that defines a sayHello()
function, which main()
calls. If you indicate the variable type, as in this example, a type test is performed later on. In addition to floating-point numbers (double
) are integers (int
). Both are subtypes of num
, which supports the basic addition (+
), subtraction (-
), multiplication (*
), and division (/
) operations.
Dart also supports strings (String
) and Boolean values (bool
). A long string can be spread across multiple lines. Strings use UTF-16 encoding by default. If you want a variable to store arbitrary values, you declared it with var
, as in JavaScript. Dart uses the $
notation to add variable content to a string, and ${exp}
lets you integrate an entire expression (exp
) into the text.
For short functions like sayHello()
, you can use shorthand notation:
String sayHello(String name) => 'Hello $name.';
The enum
type lets you create a custom data type. In the following example, variables of type Color
assume one of three values: Color.Red
, Color.Yellow
, or Color.Blue
:
enum Color {Red, Yellow, Blue}
Dart only supports the ==
comparison operator, with shorthand notations for conditions:
<C>var result = condition? Expr1 : expr2<C>
or, even shorter,
<C>var result = expr1 ?? expr2<C>
In the latter case, if expr1
is not null
, Dart returns its value; otherwise, it returns the result of expr2
.
If Dart expects a Boolean value, only true
is evaluated as true. Unlike JavaScript code, the following thus returns nothing:
var smith = 'Mr Smith'; If (smith) print('Hi!');
In addition to arrays – referred to as lists – Dart has maps, which store key-value pairs. In addition to the for
loops known from JavaScript, programmers can use foreach()
to iterate over the elements of an object and for
… in
to iterate through lists and other iterable objects. To leave a while
loop, you use break
; continue
immediately starts the next iteration of the loop. Both keywords also occur in switch
-case
constructs.
In Dart, everything is an object. Functions store variables, which means that variables can also be used as arguments for other functions. As in CoffeeScript, you can denote parameters as optional and define default values. Dart supports anonymous (lambda) functions and closures. Types can be tested at run time with the keywords as
, is
, and is!
(i.e., is not); for example:
(p as Person).name = 'Henry';
Listing 3 shows an example of a class definition: The constructor in Dart has the same name as the class. The example also uses shorthand notation that directly assigns the values passed to x
and y
. Alternatively, the variables can be listed, wherein the assignment occurs before the constructor body is executed:
Dot(num a, num b) : x=a, y=b { ... }
Listing 3
Class Definition Example in Dart
If needed, you can name constructors and thus highlight their purpose, as shown in Listing 3 in Dot.onXAxis()
. The attached : this(x,0)
(line 6) calls the nameless constructor. Unlike CoffeeScript, Elm, and TypeScript, you can overload some operators (e.g., +
). In the listing, this ability is used to add two more dots later.
Inheritance is implemented with extends
, and super
accesses the parent class. All methods and properties are public. If their names start with an underscore (e.g., _coverage
in this example), they are automatically only visible within the class (private
).
The Square
class in Listing 3 demonstrates an abbreviated notation for get
and set
methods. Additionally, you can create constant objects that are immutable in the course of the program. Properties and methods marked as static
are shared by all objects in a class.
Factory constructors are used to ensure that only a single or a specific object of the class exists; mixins let you combine multiple classes in a single class:
class Car extends Engine with Body { //... }
In Listing 3, if you replaced extends
with implements
, Square
would inherit the interface from Dot
, but not the implementations. Dart can derive classes from those classes tagged as abstract
but cannot create any objects.
If necessary, you can initiate access to non-existing methods in Dart. To do so, you only need to implement the appropriate class method:
noSuchMethod(Invocation mirror)
If the class provides a method named call()
, both its object and its function can be called.
Derived classes can override the methods of the parent class with their own versions. To do this, you need to tag them with @override
:
class Rectangle extends Dot { @override void draw() { ... } }
Besides @override
, Dart currently supports other annotations – known as metadata. Dart developers should no longer use methods tagged as @deprecated
. Finally, objects can be produced from JSON data:
var dot = JSON.decode('{"x":1, "y":2}');
Dart also supports generics. When declaring a class or function, it is not clear which values one of its objects will process later; thus, you need to use a placeholder (e.g., T
in the example that follows). You only define the type when creating the object; extends
(in the angle brackets) lets you assign types to specific (sub)classes:
class Car<T extends Daimler> { T drive(T model) { ... }; } var mb = new Car<Mercedes>();
Dart is oriented on Java when it comes to handling exceptions; for example, you use throw
to create an object and catch
to field it. Dart provides prebuilt error
and exception
classes for error handling, from which you can derive your own classes.
Dart supports asynchronous programming: A function tagged async
returns immediately before Dart has run the statements contained in the function's body. Similarly, a function call tagged await
waits until the asynchronous function has finished its work. The future API will offer a concept similar to Promise
objects in ES2015.
Dart code can be outsourced to libraries, if needed, with each library using its own namespace. All functions and variables that start with an underscore in a library are visible and can be used only within the corresponding library. The import
statement integrates libraries, which can reside on other servers, into a Dart script.
Dart even supports lazy loading, in which the Dart program does not load the library until it actually needs it. Existing JavaScript libraries can only be addressed via an API.
Elm
Evan Czaplicki created Elm [12] in 2012; the Elm Software Foundation is now responsible for development. In contrast to the other alternatives, Elm is a functional programming language. Although a compiler translates the code written in Elm to JavaScript code, existing JavaScript libraries can only be used with a special interface.
The compiler is available under a BSD-style license and is written in Haskell. It not only points out errors, it also delivers solutions (friendly error messages). As an alternative to the compiler, you can use a special console (a REPL, or Read-Eval-Print Loop shell), that directly executes the Elm code you type. Plugins are available for several popular editors, such as Atom, IntelliJ IDEA [13], and Emacs. If you are interested, you can try out Elm in an online editor [14].
Elm itself only supports a few constructs and keywords. When you define a variable, you can also specify the type. Elm distinguishes between Booleans, integers, floating-point numbers, characters, and strings:
counter: int counter = 42
The compiler automatically infers the type of the variable and warns of problems. The ++
operator lets Elm developers weld strings together. The language distinguishes between integer division (/
) and floating point division (//
). Comments start with --
. Lists are similar to JavaScript arrays:
colors = ["Red", "Blue", "Yellow"]
Lists can be manipulated with functions from a supplied library. For example, List.sort colors
sorts the colors
list. Tuples, such as ("Henry", 32)
contain a fixed number of random values. Records take several variables; the dot operator points to the value of a variable:
dot = { x = 1, y = 2 } dot.x
Alternatively, you can use .x dot
to tell Elm to retrieve the variable x
from the dot
record. To modify the values in a record, use {dot | x = 3}
. All record manipulations are non-destructive: Elm does not change the value of x
in the dot
record but generates a completely new record. The compiler ensures efficient use of memory. In functions, you use the record elements directly:
dist {x,y} = sqrt (x^2 + y^2)
Finally, union types correspond to enum
in Dart. In the following example, the new Color
data type is created, from which variables can assume the value red
, yellow
, or blue
.
type Color = Red | Yellow | Blue
Functions can be defined without the usual brackets, which are also missing in calls. You only need to use spaces to separate several parameters:
square n = n * n
If you want to avoid calling nested functions, you can use the pipe operator (|
) to write them one after another. Elm supports anonymous, or lambda, functions, such as: \n -> n/2
. The \
is followed by the name of the parameter, and the arrow is followed by the function part. Conditions are also short and sweet:
isPositive number = if number > 0 then "Positive" else "Not positive"
Alternatively, you can use case
… of
(as shown in Figure 3), and you can limit the visibility of variables to a specific expression with let
… in
. This essentially describes the language scope. The built-in functions let you design a complete web application and support access to HTML code. They consistently use a model-view-controller concept, which is referred to as the Elm architecture (Figure 3).
Buy this article as PDF
(incl. VAT)