Hello, Elm!

March 21, 2016

I’ve been curious about the Elm programming language ever since I saw a talk on functional reactive programming at Clojure/West last year. Since I mostly do backend development for work I havn’t had a good opportunity to try it out. A couple weekends ago I finally decided to try my hand at learning the language by building a simple game, and I was shocked at how quickly I was able to make progress. I’m impressed with the Elm documentation and in particular the examples provided on the language’s website, which made my experiments with Elm the smoothest start I’ve had with any programming language, let alone with a new programming paradigm like FRP.

Elm presents examples incredibly well – a good simple one is the demonstration of the mouse position signal. The example appears on one side of the page, juxtaposed with the code powering it on the other side in an in-browser editor. You can hot swap or recompile code in order to experiment frictionlessly with the example. I don’t think I’ve ever seen tutorials or examples on a programming language’s website that have had as much thought and care as clearly went into Elm’s.

Coming from a little bit of experience with Haskell, which inspired Elm’s syntax, I expected Elm to have a similarly heavyweight (and powerful) type system. Haskell’s type system, if you haven’t used it, provides powerful gurantees and catches lots of bugs but also tends to get cumbersome during computations involving side effects, IO, or control flow structures like error handling. Adding IO to a function, for example, requires changing its type signature and also the type signatures of every function calling it.

By contrast, the Elm type system provided a lot of the same help without getting in the way nearly as much. There are a couple good design choices that help it stay streamlined and lightweight. First, Elm dispenses with the monads that dominate idiomatic Haskell. Instead, the abstraction of a signal – a value that changes over time – provides a clean interface from a purely-fucntional to the outside world. Meanwhile, Elm’s elegant record syntax and record-based polymorphism solve many of the abstraction problems that Haskell solves by deploying higher-kinded types and typeclasses. Elm’s type system is not as powerful as Haskell’s without HKTs and typeclasses, but I found myself happy to let them go for the time being. Language creator Evan Czaplicki has clearly thought through these features of the language carefully and with an eye towards simplicity and clean language design. For an example of the kinds of discussions that go into this, see this discussion of adding higher-kinded polymorphism or typeclasses into Elm.

The Elm architecture is worth a brief mention as well. It’s not a feature of the language per se, but it is one of the things that makes Elm a joy to work with. The basic idea is that your code is divded into three parts:

You wire these together by defining your inputs and outputs as Signals. For my game, for example, the primary signal looks like this:

delta = Signal.map Time.inSeconds (Time.fps 35)

input : Signal Input
input =
  Signal.sampleOn delta <|
    Signal.map4 Input
      delta
      Keyboard.enter
      Keyboard.space
      Keyboard.arrows

Here, Input is a constructor that builds an Input data structure from the signals delta (which delivers a series of time deltas targeting the given number of frames per second – 35, in my case) and the three Keyboard signals (which change when the state of those keys – up or down – changes.

Defining this was the trickiest part of the program, and there are still a few warts in it (more on that in another post) but it comes with a major benefit. It’s now possible to wire together the pure update and view functions like this:

gameState : Signal Game
gameState = Signal.foldp update newGame input

main = Signal.map2 view Window.dimensions gameState

newGame creates an empty Game data structure, while Signal.foldp creates what’s called a “past-dependent” signal – essentially the game loop, stepping the state forward through time as the input signal changes. It took me a little while to get my head around this with the help of Elm’s examples, but I now find it elegant and satisfying code.

The bottom line is Elm made it fun to make things. Over the course of a few hours, I was able to create a bare-bones Asteroids clone that I’ve tinkered with over the past couple of weeks to refine. You can see it here and the code is on Github.