Intro to Elm
In this blog post I’ll be covering Elm, an up and coming functional programming language for building web application front ends. Elm compiles into JavaScript and utilizes virtual DOM technology to efficiently render web UIs. Elm focuses on simplicity for the developer with a helpful compiler and an easy to understand application architecture.
Building web apps with Elm is very different than traditional front end development with JavaScript. The biggest of these differences is the fact that Elm is statically typed. This means variables have a type that cannot be changed once it has been declared. Attempting to pass an integer to a function that expects a string will not compile in Elm. At first static typing may seem like a drawback, part of the appeal of JavaScript is its flexibility. However, the rigidity of static typing provides many benefits including finding many errors before a program is even run.
Elm is also a functional language. Functions in Elm are considered “pure functions” meaning they don’t have any “side effects”. Passing the same value to a pure function twice will return the same value each time. Additionally pure functions aren’t allowed to mutate state outside of their scope. Elm actually takes this no mutations mentality to the extreme, that is to say all values in Elm are immutable. If you want to modify a value, you must first make a copy; the original is never changed. Immutability seems scary at first, but it leads to better structured applications.
Elm applications usually follow a pattern for application structure called The Elm Architecture (TEA). TEA breaks applications into three distinct parts: Model, Update, and View. Model is the data structure containing the state of your application. Update is a function that takes your current model and an action as parameters then returns a new updated model. Similar to Update, View is a function that takes your app Model as a parameter and returns the HTML for that particular application state. The Elm core library includes a set of “program” functions that take your Model, Update and View. Elm will call Update when a new message is received, and will call View when your Model has been updated.
Now that we’ve had a high level introduction to Elm, let’s take a look at some code. I’ve built a small web app that uses the Spotify API to search for tracks and display some basic info about them. The full code for the project can be found here and an actual demo of the application can be found here.
We’ll start by creating some type definitions for album and track data returned by Spotify:
type alias SpotifyAlbum =
{ name : String
, images : List String
}
type alias SpotifyTrack =
{ album : SpotifyAlbum
, artists : List String
, name : String
}
type alias TrackList =
List SpotifyTrack
SpotifyAlbum is our most basic type consisting of a name for the album and a list of image urls. SpotifyTrack uses our SpotifyAlbum type to declare it’s album data, as well as a list of artists and the title track itself. Declaring TrackList as a type allows us to more conveniently refer to a List of SpotifyTracks later on.
The idea for our next bit of code is borrowed from the excellent blog post by Kris Jenkins about Elm and UI anti-patterns.
type RemoteData e a
= NotAsked
| Loading
| Failure e
| Success a
type alias SpotifyData =
RemoteData Http.Error TrackList
RemoteData is a union type, which is a bit different from the types we have seen so far. Union types allow you to declare that a type could have several possible shapes. Our RemoteData union type could be NotAsked, Loading, a failure state or a successful state. We then use RemoteData to build SpotifyData where our failure state is a Http error and our success state is a TrackList.
This brings us to our Model:
type alias Model =
{ query : String
, tracks : SpotifyData
}
The first part of our model is a string to keep track of the search query the user types into our app. Since we already did the work of building up our data types, we can store our track data as a SpotifyData type.
Before we take a look at update and view we need to set up one more type, Msg.
type Msg
= UpdateQuery String
| Search
| SearchResult SpotifyData
Msg is the all the different messages(actions) that our app should be able to handle. Msg is a union type, since it can have several possible shapes. UpdateQuery is used to update the search query when a user types something in the input. The Search Msg is for when the user actually clicks the search button, telling our app to fire off an API request. Finally we have SearchResult which indicates to your update function that new data has been returned from the Spotify API.
This brings us to our update function:
update msg model =
case msg of
UpdateQuery query ->
( { model | query = query }, Cmd.none )
Search ->
( { model | tracks = Loading }, fetchSpotify model.query )
SearchResult tracks ->
( { model | tracks = tracks }, Cmd.none )
Update is very simple, handling each possible type msg could be. When using a case statement with a union you must declare code to handle all possible shapes a union type may have, or the program will not compile. This restriction gives you the benefit of knowing that you code will never have any unhandled possibilities.
Finally we arrive at the view function.
view model =
div [ class "container is-fluid" ]
[ h1 [ class "title" ] [ text "Spotify Search" ]
, div [ class "columns" ]
[ input [ class "input column is-4", placeholder "Search track title", onInput UpdateQuery ] []
, button [ class "button is-primary", onClick Search ] [ text "Search" ]
]
, spotifyView model
]
The main view function itself is very small. It is primarily is responsible for creating the main app html including the search input and button. The Html for the grid of tracks is in a function called spotifyView that will return the correct grid state based on our model. Splitting the view function into smaller pieces like this helps keep our program readable. Additionally, it has the benefit of allowing you to think about your UI in smaller chunks rather than a bigger whole. You can see the spotifyView function in its entirety on github.
If you’re at all interested in Elm I encourage you to check out further tutorials on the official Elm Site. Several cities host Elm meetups and the first ever elm-conf is coming up in September. You can also join the Elm Slack channel, where plenty of developers hang out to talk Elm and are willing to help out and answer questions.
1 Comment