Experimenting with Elm

Reflections on using a purely functional programming language for web development

Ben Lyon
Slalom Build
8 min readJul 3, 2018

--

DISCLAIMER: I have very limited Elm experience, having only worked with it for about a week in order to make this example application. Some of the conclusions I draw about the limitations of Elm may be inaccurate, due to my limited exposure.

What is Elm?

For those that don’t know, Elm is a purely functional programming language designed for writing web applications. It compiles to JavaScript, and thus can make use of any other JavaScript library, and can be embedded within JavaScript projects. Due to type inference and the language’s functional nature, virtually all errors are caught at compile time, resulting in no runtime errors. Additionally, Elm has its own virtual DOM implementation, and boasts benchmark times lower than any other major JavaScript framework.

See elm-lang.org for more information.

Our Application

During Slalom’s “Hack the Office 2017” hackathon, I worked with fellow Slalomites Josh Horecny, Ian Doane, Myung Kim, and Ali Ferrell to build a simple web application that relies on a Java Spring backend to integrate meeting room booking information from Outlook with motion sensor data from each of the meeting rooms. It then displays which rooms are booked but empty, or empty but occupied, and so forth. We chose Elm for the features described above, as well as the fact that the hackathon provided a good opportunity to learn a new language. Here’s what the finished application looked like:

The home page
Individual room page

General Elm Application Structure

Before we dive in to the guts of our app specifically, let’s go over the general structure of an Elm application, as it is a different pattern than some other web frameworks. The average project is separated out into several key files. (For a more in-depth look at Elm syntax, patterns, and project structure, see elm-tutorial.org)

· Main: creates and returns an HTML program object that encapsulates the HTML element to draw on the page as well as the logic to update it. References all of the other files either explicitly or transitively while constructing the program

· Models: self-explanatory, same as other web frameworks (though Elm convention tends to have one file for all models in a particular app or submodule, rather than a separate file for each model)

· Messages (or Msgs): place to define events that happen in the application that other parts will respond to. Examples include a message indicating when a page has finished loading, or when an element is collapsed or expanded.

· Commands: place to group things that may involve side-effects, such as HTTP calls, or saving things to local storage

· Routing: fairly self-explanatory, matches the url string to the particular Route type defined in our Models.elm

· Subscriptions: used to listen for and respond to external events, like keyboard input or route changes

· Update: receives messages and performs a change on the Model, returning a new, updated Model (much like the reducer in React/Redux)

· View: the HTML template(s) written in Elm

Note that for more complex projects, these groupings may be split across several modules in order to avoid monolithic files. E.g. ModuleA.Model, ModuleA.Update, ModuleA.View, ModuleB.Model, ModuleB.Update, etc.

Our Application Structure

Here’s how our meeting room availability app (dubbed “Hodor”) fit to Elm’s patterns:

· Main: sets our initial state, hooks up View, Update, and Subscriptions

· Models: contains our parent model which gets passed to all pages, as well as models describing meeting rooms, meeting schedules, and room availability

· Msgs: defines messages for when the list of meeting rooms starts/finishes loading, when the schedule for an individual room starts/finishes loading, when the route changes, and a few others

· Commands: contains all of our HTTP calls out to the Outlook API wrapper, as well as the methods necessary to deserialize the JSON response

· Routing: defines routes for the main page and for an individual room page

· Subscriptions: our only subscription is for updating the clock that is displayed in the app

· Update: fires off the HTTP call when the load meeting rooms message comes through, updates the Model with the results of the call when the finished loading message comes through. Same for loading the schedule of an individual room

· View: our HTML templates for the main page and room pages, as well as reusable elements like the page header and footer

Challenges

As this was the first Elm project any of us had ever worked on, it’s not surprising that we had some difficulties getting things to work the way we wanted. Here are some of the issues we ran into:

1. Making an Elm app with multiple pages can be kind of clunky

In order to render a page, Elm must be passed a parent Model object that contains the page state. When the state changes, the Model gets updated and the page re-rendered. The flow looks like this:

https://www.elm-tutorial.org/en/02-elm-arch/04-flow.html

The issue that we ran into is that every page in the application must be passed the same parent Model. This means that the Model has to contain the state of every page in the application. Thus for any given page, many of the Model fields are not used and must be set to null or empty values. It’s also worth noting that since Elm is purely functional, a new Model must be initialized on each update, rather than mutating the existing state.

For our app, we had a home page for displaying all of the meeting rooms in our office and each one’s availability, and then a page showing the meeting schedule for a specific room when you click on it. Our Main.elm ended up looking like this:

Our parent model contains:

· A list of meeting rooms (used by the home page)

· A RoomSchedule object that associates a MeetingRoom with a list of ScheduledMeetings (used by an individual room page)

· The Route for the current page

· An autoUpdate flag specifying if the page should regularly refresh itself (used by an individual room page)

· The current time (used by an individual room page)

· A filter for showing rooms by floor number (used by home page)

As you can see, the method signature, implementation, and calling of the init function are all long and ugly, as we are required to provide every field used by every page. This also causes our Update.elm logic to be quite ugly as well, as when we load the home page we need to supply an empty RoomSchedule object:

At the time of the hackathon, I remember being very dissatisfied with this pattern, and trying to find better ways to manage multi-page Elm applications but coming up short. I’ve since taken a look at the sample applications on the Elm home site (I don’t think those were there when I was looking before), and found a few patterns that we could use to clean up this mess a bit. Basically, we just extract all of the Model initialization into a predefined object that we can stick in our Models.elm and then reference from Main.elm. This initialModel would look like:

And then our Main.elm would look like

This way, it’s much easier to see what field the myriad of empty strings, Falses, and empty lists are supposed to correspond to, and also allows us to reuse some of those initial value objects in our Update.elm. We still have to pass empty objects through to pages that don’t make use of them and define an “empty” version of every object we pass around, but at least it’s much cleaner and easier to understand than before.

2. Elm doesn’t have a lot of functionality “built-in”

Many simple things that I take for granted in languages like Java or JavaScript are not innate to Elm and must be either written “from scratch”, so to speak, or obtained by including a separate Elm library (or JavaScript library). For example, in our app we needed to compare dates to each other and format them when displaying them. Elm has a Date type, but there is no good way to either compare Dates or format them. We had to pull in a separate library in order to get the necessary functionality.

The act of including a third-party library was relatively painless, but when looking for the best option, I couldn’t help but notice that there are a lot of Elm libraries that didn’t have much by way of documentation. Our app is quite simple, but for a more complex app that needs to rely on a larger number of differently libraries, I could see there being problems with not having adequate documentation to rely on.

It’s worth pointing out that since Elm compiles down to JavaScript, any JavaScript library that provides the functionality you’re looking for is fair game. Additionally, Elm provides several options for interfacing with JavaScript libraries in order to bridge these gaps. This will certainly alleviate the issue of limited Elm library choices and documentation, but it also means that you may be sacrificing some of the benefits that Elm provides, such as the speed and stability that come from its functional patterns.

Final Thoughts

I enjoyed learning Elm for this hackathon project. It was fun to be able to use functional programming patterns on a web application. The project structure took a little while to get used to, but I found that once I got accustomed to the patterns, it made things pretty clean and easy to follow the application flow — for the most part. There were definitely parts that could have been cleaner/clearer, such as the aforementioned parent Model and init function. Also, our View.elm was getting pretty monolithic towards the end, and could have benefitted from being split out into a file for each page.

True to the claim, there were no runtime errors in our app. There were a few times when the backend Spring API failed and spit out a 500 error, but there was never a point at which the Elm code we had written failed during runtime. Generally, the errors that were generated during compile time were helpful in pointing me in the right direction and suggesting fixes to the root problem.

Though I did enjoy writing this application in Elm, I think that I would want to re-write it in Angular if the app going to continue to be used by Slalom. This particular use case doesn’t really benefit from the increased speed and stability that Elm has to offer, and it suffers from the complexity and level of obfuscation that Elm causes. If anyone else wanted to add new features to this app in the future, I think Elm would make that a challenge.

So then, are there any use cases where Elm is the right choice? I would say yes. Simple, single page web apps where speed and stability are a priority are prime candidates for Elm. Also, due to Elm’s JavaScript interoperability, there may be cases where a specific piece of a larger, more complex app (such as a microservice or what have you) could benefit from being re-written in Elm. At the time of writing, Elm is still a young language — it first debuted in 2012 — and I would hope that some of the issues we encountered with limited Elm libraries and documentation will decrease as the language matures. For anyone interested in functional programming, web frameworks, and/or high-performance web applications, it’s certainly worth keeping an eye out for Elm.

--

--