Want to see the full-length video right now for free?
Sign In with GitHub for Free AccessElm is a programming language targeted at the front end (runs in a browser, not on Node) that offers a different take on building dynamic web applications. It was designed by Evan Czaplicki to provide "the best of functional programming in your browser".
Some of the more noteworthy features include:
The Elm Architecture is the recommended structure for building Elm applications. You can see the full source for the sample game in this gist, but to dive into the architecture, it consists of three main parts:
A Model
in Elm is a "record", similar to a Ruby hash, which defines the
shape of the data our application will operate on.
type alias Model = { x : Float, y : Float, radius: Float }
So here we have our model which will contain the x and y coordinates of our ball, as well the radius.
The update
function knows how to respond to any input, or "Action" in Elm
speak. Before diving into update
itself, we can see how an Elm application
defines the possible actions with a "union type" defined for the
possible actions. This serves as a great index of the interactive aspects of
an application. For instance, for the simple game in which the user can move
the ball up, right, or left, we would have an Action
type like:
type Action = Left | Up | Right | Noop
From there, we can view the update
function. The actual form in the full
program is slightly more advanced, but this is a simplified form that ignores
the gravity effect:
update : Action -> Model -> Model
update action model =
case action of
Noop -> model
Up -> { model | y <- model.y + 30 }
Right -> { model | x <- model.x + 5 }
Left -> { model | x <- model.x - 5 }
The first line of this snippet is the "type annotation" which hints to both
any reader (e.g. you 3 weeks from now) and to the compiler what the types of
this function are. In this case, we have a function that takes an Action
and a Model
and returns a Model
.
On the next line, we define the actual function, naming the Action
argument
action
, and the Model
model
(how clever!). We then have a case statement
which will select which code branch to apply based on the incoming action.
Lastly, for each action, we need to build a new version of the state that has
been changed by the action. Note, since Elm is purely function, we can't
mutate the state, but instead we need to produce a new state instance with
the updated value and return that. Luckily, Elm has a pleasant and
concise syntax for producing a new state based on updating a nested value.
Since we've collected all of our logic pertaining to how the app changes over
time in our Action
type and update
function, our view function is simply
a declaration of how to render the current state of our application. For
those familiar with React, this is the same approach, but taken to the
extreme.
view : Model -> Element
view model =
let
ball =
circle model.radius
|> filled blue
|> move (model.x, model.y)
background =
rect (toFloat width) (toFloat height)
|> filled green
in
collage width height [ ball ]
Here again we start with a type annotation; the view
is a function taking a
Model
as the sole input, and returning an Element
. In this case Element
is a canvas element, but our view could just as easily return HTML elements.
From there, we define the body of the function, building a ball
and
background
canvas elements, and returning a combined "collage" (Elm canvas
wrapper) for rendering.
With our program defined, now we can try it out. Elm has a utility called the
Reactor which can live compile and run our application in the browser. We just
run elm reactor
in the folder that contains our elm file and open the
browser to http://localhost:8000. The Reactor will present a list of all the
files. We can run an Elm file by clicking on it, and the application will now
take over the screen and run.
Alternately, we can click on the wrench icon next to the file to open our application in the Elm debugger. For this particular script we can see that the model is "watched" in the debugger sidebar allowing us to see the values at any given point in time.
In addition to watching a specific value, the Elm debugger also makes it
possible to pause and even rewind the state of our app. Since our Model
is
immutable, Elm simply holds on to each copy over time and we can rewind,
replay, and generally inspect the state of our app at any point.
There are even some fancier features like hot swapping and tracing. Hot swapping allows us to alter our code and see the new versions of our functions live applied to the stream of user inputs. Tracing makes it possible to see a graphical representation of the history of a given value. Unfortunately there are some bugs with this functionality in the current version of Elm, but you can check it out live in the Mario example on the elm-lang website. This functionality was heavily inspired by Bret Victor's talk - Inventing on Principle, which is spectacular.
One of the stand out aspects of Elm is the consistent focus on providing a pleasant and helpful feedback via the compiler and overall developer experience. Rather than feeling like you're fighting the compiler, Elm is designed to make the compiler seem like a friend providing helpful hints to guide you in the right direction.
Recently there was a big push to further refine the type related error message to make them more friendly and useful. This update is summed up well in the blog post Compiler Warnings.
This works well for both simple typos, for which Elm can even offer alternatives "Did you Debug.watch instead of Debug.vatch", as well as type mismatches.
Elm takes inspiration from a number of more academic languages like Haskell, but simultaneously makes sure to remain approachable and pragmatic. The author, Evan Czaplicki, is very clear about his desire to make something that is easy to get started with, and then eventually opens up to expose the more complex functional bits inside. In his talk, Let's Be Mainstream, he does a great job of summarizing why he thinks functional programming has traditionally done a poor job of selling itself and making it easy to get started, and the approach he is taking with Elm to hopefully avoid these same pitfalls.
One great example is the start-app package which makes it very easy to
build an application using the Elm Architecture approach of Model, View,
Update, and then letting start-app
take care of wiring those functions up
to the real world with all its messy side effects and mutability.
Additionally, because of the focus of the front end and targeting the browser as the primary run time, we're in a position to make "real apps" that do "real things", rather than having to play around with simple toy command line apps.
Elm comes with a standard library, but it is reasonably conservative in what
it includes. Instead, things as core as the HTML library are provided via
packages. Even the standard library, elm-core
, is distributed as a package.
The package manager is a first-class par of the Elm ecosystem.
One great feature of the package system is that it is able to enforce semver to some extent. It does this by inspecting and comparing the type signatures of functions between versions and ensuring the larger changes are sufficiently flagged via the version bump. Prettttttyyy cooool.
While Elm is still relatively young relative other programming languages, there is an interesting convergence happening with a number of the ideas Elm is built on. React embraces the idea of your UI being a function on your application state, Redux is a state management library for React that is very similar to the Elm Architecture, Om is a ClojurScript interface to React, and Immutable JS is a library from Facebook for bringing immutability to our JS apps.
If you're interested in getting started with Elm, start with the install guide, then check out the Quick Start guides on the Elm Lang docs site. The Elm Architecture guide is particularly interesting, if only to get exposure to that approach to building dynamic UIs.