❮ Back to index

Experience report: Rocket lander in Elm

Writing proposals? Try Proppy, our new product!

Reading time: ~10 minutes.

Writing anything substantial in JavaScript is hard because JavaScript lacks many of the tools programmers expect these days, like a coherent and strong type system.

For that reason we are investigating various new compile-to-JavaScript languages. In this post we're looking at Elm. To get a better feel for the language I implemented a small game:

Play the demo (desktop only, sorry!)

First though, why is JavaScript hard? Consider the following cases:

> "hi" * 0
NaN
> "2" * 0
0
> "2" + 0
"20"
> null - undefined
NaN

The famous Wat video has more examples after the brief part about Ruby.

People noticed this of course, and to fix these issues they started developing new languages that transpile to JavaScript. Some well-known ones are CoffeeScript, TypeScript and Dart.

I have spent a reasonable amount of time with all of the above, writing actual production code. I consider all of them an improvement over the very low bar that is JavaScript, but none of them get me excited.

Picking e.g. TypeScript: It is high-ceremony to define interfaces, and the type-safety only helps if a sufficiently large amount of code is type-annotated. It's like a phase change: Below a certain threshold one might as well just omit all annotations because they help so little. Luckily a large body of type annotations has been developed already.

The following snippet is a simplified version of a production bug we had because of an intermediate function with an any type argument:

function mul(a: number, b: number) {
  return a * b;
}

function anymul(a: any, b: any) {
  return mul(a, b);
}

// The following doesn't compile and that's great.
console.log(mul([], 0)); // Breaks!

// This does compile though:
console.log(anymul([], 0));

In the last few years the space of compile-to-JavaScript got more crowded, and a lot of the new entrants are either based on more principled languages like Haskell, or compile well-typed languages directly. To name a few: PureScript, js_of_ocaml, ghcjs, Elm, Fay, clojurescript, scala.js.

Enter Elm

Elm is a statically typed, functional language with immutable data structures and first-class support for functional reactive programming.

Elm also comes with a standard library that contains lots of useful tools such as an implementation of the virtual-DOM, famous for making React so speedy.

Like in Haskell type annotations can be given inline, or in a line above a function, and types always start with upper case:

1 : Int
True : Bool
["ab", "cde"] : List String

mul : Int -> Int -> Int
mul a b = a * b

Elm starts execution at the main entry point:

import Text

main = Text.plainText "Hello from Elm."

To see more code check out our rocket lander game on GitHub

To get going I recommend installing Elm via Haskell's Cabal-Install which is available on most distributions:

cabal update
cabal install elm-make elm-package elm-compiler

This is not a tutorial so I refer to Elm's own documentation to learn more.

FRP

Elm's functional reactive programming (FRP) connects inputs ("signals") to code that depends on these signals, forming a directed graph in the process.

Anything that generates DOM events can be a signal in Elm. E.g. the mouse moving or a key press. Elm also has a signal that generates a steady tick, e.g. at 30 frames-per-second, which is useful for programming games. Edit: As Jason Merrill points out in the comments there is fpsWhen which allows switchting of the fps updates. Thanks Jason!

Every signal in Elm propagates to all code that depends on it. The flow is usually something like (Input -> Update application state -> Redraw application).

Elm requires setting up the entire signal graph before the application starts. That makes it easier to reason about inputs flowing through the code, but it also comes with some downsides:

E.g. for the rocket lander demo I need a 30 frames-per-second signal to animate the game. I have to set that up at game start and can't switch it off. I.e. the help-screen at the beginning is re-rendering 30 times a second, even though nothing changes.

For a silly little Game that's probably fine. Consider though: An app that deals with user input will see a few events a second with long pauses of nothing happening. If you wanted to add an animation that fades out at 30 frames per second then the app would need to run at 30 FPS the whole time. In these cases it's best to use JavaScript directly via Elm's (ports).

See this video from Strange Loop 2014 for why the FRP graph is fixed.

Maturity

Elm is new, and it shows. There are very few libraries. There are also bugs: Despite the promise of producing error-free JavaScript I managed to create a few compiling-but-broken programs. E.g. the following compiles,

 ship = shipUpdate
           game.gravity
           (if game.ship.fuel > 0 then arrows.y == 1 else False)
           (if game.ship.fuel > 0 then arrows.x else 0)
           ship

but throws a "ship is undefined" error in JavaScript. I used ship instead of game.ship in the last line by accident.

Interacting with the outside world through WebSockets and HTTP works but is not well thought out. E.g. there is no error handling for WebSockets. Elm's main author, Evan Czaplicki, has some good ideas for improving this with a promise-API so I don't think this will be a problem for much longer.

Would use again

Despite sounding critical my experience was very smooth. Elm requires code to be explicit about state, and cheating is very hard. After a few hours of getting used to that I reaped the benefits in form of a game that worked immediately.

I believe that Elm will be usable for non-game programming at some point, but it's still missing a few bits like the promise API I mentioned above.

Just as I finished writing this Evan released an excellent tutorial on how to architect apps in Elm