Gotta Catch an Elm

EJ Mitchell

When I first started at thoughtbot, my primary experience was in Rails and TypeScript. However, the first project I joined was written in Elm and Scala. Both are pretty different from my “native” languages: they are strictly typed and utilize functional programming patterns. But, getting onboarded with Elm was much easier than with Scala. There were many reasons for this, one of them being that Elm has a famously descriptive and easy-to-use compiler that gave me very direct messages about what I needed to fix. Despite this, though, I often found myself wondering why things had to be done in such specific ways. What was a Cmd? What was a Msg? Why did I even need them? I resolved that the best way to find out was to build something every 90’s kid dreamed of having.

If you guessed a Pokedex, then you either really wanted one too or you just read the title to my post. Either way, good job!

It turns out that what goes into a Pokedex is perfect for a beginner app. All you need to be able to do is search for the name of a Pokemon and display the information you want about that Pokemon. Conveniently - as if I planned it or something - there is also a well laid out and detailed API called the PokeApi.

With that in mind, my next step (apart from reading the Elm docs, of course), was to figure out what I wanted on the page. In addition to a search bar, I decided that I was going to also make a “Get Random Pokemon” button, which would introduce me to how Elm handles random numbers - since, spoiler alert, random numbers are handled differently in functional programming languages.

So, written out, here are the steps I followed to make the app:

  1. Set up the configuration to get a simple “hello world” page.
  2. Build a “Get Pokemon” button that only returned the PokeAPI response from one URL.
  3. Expand from a “Get Pokemon” to “Get Random Pokemon” button.
  4. Build a simple decoder that takes the API response and returns just a name.
  5. Add onto that decoder to get more deeply nested values from the response and build a Pokemon record.
  6. Move on to making a search bar, using Elm’s onInput functionality.

It seems like this would be slow, but it worked well to better understand some idiosyncrasies in Elm. Of course, there were some gotchas to this process that were unique to Elm that made things slower. However, there were also some victories to using this elegant, strictly-typed language that really made me feel like I was catching ‘em all.

Starting the app and getting a “Hello World” page

After installing Elm, all you need to do is go to your project folder and type elm init. That’s it. o(≧▽≦)o

Typing elm init gets a basic folder structure started - it will prompt you to make an elm.json file which keeps track of the packages you use (like package.json does for npm projects). It will also make an src folder where it will look for files you’ve created in your Elm project.

From here all I needed to do was set up a server and a Main.src file. Depending on how you set up your project, the compiler might also give you further steps to follow. As I’ve mentioned at the beginning of this post, Elm is thankfully really good in this department. You will almost never be left scratching your head at a response from the compiler. Here’s an example of the elm init output in the terminal:

❯ elm init
Hello! Elm projects always start with an elm.json file. I can create them!

Now you may be wondering, what will be in this file? How do I add Elm files to
my project? How do I see it in the browser? How will my code grow? Do I need
more directories? What about tests? Etc.

Check out <https://elm-lang.org/0.19.0/init> for all the answers!

Knowing all that, would you like me to create an elm.json file now? [Y/n]:

Personally, I used nginx for the server, since I’ve used it for previous projects and knew how to set it up quickly. There are other options here like Create Elm App, Elm Live, and Parcel. However, if you are doing something super simple, you can also just run elm reactor with no extra packages needed.

To get a Main.elm file started, I followed the example on this page, since I knew my end goal was to serve the result of a decoded JSON response and nothing super fancy. I cut out everything but one Model variant to have a result like this:

import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)

-- MAIN

main =
  Browser.element
    { init = init
    , update = update
    , subscriptions = subscriptions
    , view = view
    }

-- MODEL

type Model
  = HelloWorld

init : () -> (Model, Cmd Msg)
init _ =
  (HelloWorld, Cmd.none)


-- UPDATE

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  (HelloWorld, Cmd.none)

-- SUBSCRIPTIONS

subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none


-- VIEW

view : Model -> Html Msg
view model =
  div []
    [ h2 [] [ text "Hello world!" ]]

From there, all I had to do was run elm make Main.src and start up my server.

TL;DR: Elm makes starting up very straightforward, and there are many ways to serve and tweak your configuration to make it even easier for you and your team.

Fetching raw data from an API

Elm also makes this process pretty straightforward. I needed to make sure my view had a button with an onClick event handler. If you come from something like React or Angular, onClick acts very much the same to what you know, though instead of calling a function right there, Elm sends a “message” to its update function. In other words, instead of making a new function called getMeAPokemon() that handles all of the logic, I needed to create some new “messages” and “commands” in the update function of my app.

What does this mean? This is a part of the Elm architecture. Of all things, this was the hardest to wrap my head around at first. What makes Elm different from the pack, and what might take some extra mental chewing, is that you send “messages” around in your app when things change states, and paired with those messages are “commands”. These commands are sometimes side-effects that do something outside of your app. In this app, as you will see below, it is to complete an HTTP request. Sometimes these commands are just… well… nothing. This is reflected as Cmd.none, which means that the process being completed has no side-effect.

Here, the onClick triggers a Msg, which is sent to the update function. The Msg waves its arms saying “Hey, this button was clicked!“ The update function then knows what to do with this information based on the case statement. The case statement then directs you to the Cmd that makes a GET request to the PokeAPI and returns its response. When I get that response (a Result), another Msg waves its arms saying “Now I’ve got something from the API”. This Msg (RandomPokemonResponse) is then sent back to the update function and, like before, it has a branch in the case statement with the appropriate course of action.

What makes RandomPokemonResponse only slightly different than the first Msg we saw is that the Result tags along with it. This is so the Result can be dealt with as well: showing the user the info about the Pokemon if the Result is "good” or showing an error when the Result is “bad”. That’s it. Hopefully this peels back some of the noise around the architecture, which definitely confused me the first time I used it. (Even while I was trying to write this blog post!) Here is the code below:

type Msg
= RandomButtonClicked
| RandomPokemonResponse (Result Http.Error String)

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
  case msg of
    RandomButtonClicked ->
      -- Loading is a Model, like HelloWorld except it has the page say "Loading..."
      ( Loading
      , Http.get
        { url = "https://pokeapi.co/api/v2/pokemon/ditto/"
        , expect = Http.expectString RandomPokemonResponse
        }
      )
    RandomPokemonResponse result ->
      case result of
        Ok fullText ->
          ( Success fullText, Cmd.none )

The view function has a bit of HTML (written out in Elm, which is a point we will get to later) that looks like this:

div []
  [ text "Search for a Pokémon:"
  , button [ onClick RandomButtonClicked ][ text "Random Pokemon"]
  ]

TL;DR: Unless you’ve written a Redux reducer, this is likely not an architecture you’ve seen before. This pattern may be what you spend the longest getting used to.

Getting a Random Pokemon

From here, I changed the button to get a random Pokemon instead of just Ditto all the time. This required some finagling, since Elm is a functional programming language. It requires functions to be pure.

Getting a random number is inherently impure: it gets a different number every time you call it. Pure functions don’t act like that - if you give a pure function an input x and it returns y, it will always return y with input x. To get around this, I needed to lean heavily on the aforementioned Elm architecture to do what I wanted. You can see the extent of my changes to make this happen in the git diff of Main.elm:

 type Msg
     = RandomButtonClicked
+    | ReceiveRandomId Int
     | RandomPokemonResponse (Result Http.Error String)

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
         RandomButtonClicked ->
             ( Loading
-            , Http.get
-                { url = "https://pokeapi.co/api/v2/pokemon/ditto/"
-                , expect = Http.expectString RandomPokemonResponse
-                }
+            , Random.generate ReceiveRandomId generateRandomNumber
             )

+        ReceiveRandomId id ->
+            buildRandomPokemonResponse id
+
          RandomPokemonResponse result ->
            case result of
                Ok fullText ->
                    ( Success fullText, Cmd.none )
                Err _ ->
                    ( Failure, Cmd.none )


+buildRandomPokemonResponse : Int -> ( Model, Cmd Msg )
+buildRandomPokemonResponse randNumber =
+    ( Loading
+    , Http.get
+        { url = "https://pokeapi.co/api/v2/pokemon/" ++ String.fromInt randNumber
+        , expect = Http.expectString RandomPokemonResponse
+        }
+    )
+
+
+generateRandomNumber : Random.Generator Int
+generateRandomNumber =
+    Random.int 1 150

Overall, when working with Elm, apart from the new architecture, you will find that you don’t always need to know bucketloads about functional programming. In a lot of ways this is great! You can just get up and running with it. Occasionally, though, there are inklings that leak through … like random numbers or HTTP requests, which are two examples you’ll see in my demo app.

When you start doing more intense things in your apps like mapping or dealing with “contexts” (e.g., Maybe), you’re gradually eased in to some of the more hairy topics that languages like Scala just throw you into. This is one of the many reasons why quite a few people at thoughtbot recommend learning Elm if you want to get your feet wet with functional programming or strictly typed languages:

Not only do you have your hand held by the compiler, but the names of more complex concepts are relatively friendly compared to other languages.

TL;DR: Elm is a functional programming language that doesn’t entirely feel like one. If you’re new to functional programming, start with Elm!

Building a Decoder Incrementally

This is notoriously something that gives people a lot of trouble in strictly typed languages. In Elm, this is no exception. I burned a lot of time working on decoders in my app. I was lucky that both Josh and Joël have written about this, which gave me a good foundation to work off of. Eventually, what I learned is to build a Decoder incrementally. Start small, then begin adding more detail as you go. In my case, I just started with the name of the Pokemon. Then, I went about adding things from more nested and complex structures within the JSON body, like moves and abilities.

I found that by starting small and thinking of each piece as a tiny building block, I was less likely to make a mistake. It also made me really think about what I wanted my Pokemon type to look like. What information was actually important to impart to the user? I found that going through the time to make a decoder for something large (with possibly unnecessary fields) in Elm would take a great deal longer, so the likelihood of me putting extra time into something a user might not want was significantly less. I ended up building a much more elegant and sparce data structure than I would have in something like TypeScript or Ruby.

type alias Pokemon =
  { name: String
  , abilities : List Data
  , moves : List Data
  }

type alias Data =
  { name : String
  , url : String
  }

Then, I was able to make a decoder for each attribute in the Pokemon data structure and use that to make a larger decoder for the Pokemon itself. Here, I got to use a map3 function, which is another neat trick from functional programming that Elm makes accessible to new programmers.

pokeDecoder : D.Decoder Pokemon
pokeDecoder =
  D.map3 Pokemon nameDecoder abilitiesDecoder movesDecoder

TL;DR: No matter the language, decoders are no fun when you first start out. With Elm, though, you are encouraged by the nature of the language to consider more simplistic data structures. This arguably makes programmers happier when working with the code. Overall, the ability to compose and reuse decoders becomes a boon the more complex your app gets. In addition, with Elm’s decoders, the shape of your data is guaranteed after it’s been decoded! No more “shotgun decoding” required (looking at you, JavaScript).

To try something new, instead of making a “Submit” button, I used Elm’s onInput functionality, which sends out its associated Msg when a user types something into the input. In this case, when a user types something into the search bar, I have a Msg that corresponds to a branch in the update function’s case statement, which sends the user’s input to HTTP.get via buildSearchedPokemonResponse. In there, if their input matches a Pokemon, information will come back about that Pokemon. If not, they will get an error message. For reference, here’s the update function mentioned above:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        RandomButtonClicked ->
            ( Loading
            , Random.generate ReceiveRandomId generateRandomNumber
            )

        ReceiveRandomId id ->
            ( Loading, pokemonRequestById id )

        ReceivePokemonResponse result ->
            case result of
                Ok pokemon ->
                    ( SuccessWithPokemon pokemon, Cmd.none )

                Err _ ->
                    ( Failure, Cmd.none )

        UserTypedInName name ->
            ( Loading, pokemonRequestByName name )

Of all the features I had planned for this app, I thought this one would take me the longest. However, with the way the Elm architecture is built, it took me about 2 minutes to wire everything up. Here is the search function in the view section of my app:

search : String -> Html Msg
search query =
    div [ class "lines__header-wrapper" ]
        [ div [ class "search__wrapper" ]
            [ input
                [ placeholder "Search for a Pokemon"
                , type_ "search"
                , value query
                , onInput UserTypedInName
                ]
                []
            ]
        ]

For first-time Elm users, writing your HTML using Elm’s HTML library is definitely a bit strange, but doing so allows you to extract your view to different, smaller blocks (sort of like the way I broke down the decoders). You can swap in different “views” to your main view function depending on what Model you return in your update function. This allows you to conditionally render chunks of your view code and encourages you to make reusable “components” for your page … sounds sorta like React, doesn’t it?

The difference between React and Elm, though, is that Elm’s reusable components are actually just functions! React seems to be heading in this direction, but it’s not as strict as Elm (yet) — components in React can still be classes that extend Component. However, in Elm, you extract reusable code only into functions - since Elm is a functional programming language from the ground up, unlike JavaScript/TypeScript.

You can also add CSS to your app, either by rolling your own stylesheet and just popping it into the index.html file like you would in basic webpages, or you can use one of Elm’s CSS packages to manage everything for you. I just rolled my own since styling wasn’t my main focus here, but elm-css seems to be one of the more popular ones.

TL;DR: Elm basically has its own version of JSX and encourages you to organize your view into components. This makes for a highly reusable and organizable UI, which React developers will appreciate.

Wrap Up

Elm is a language that is extremely conscious of first-time users. Getting up and running in it is much less difficult compared to starting a project using TypeScript - though Rails still takes the cake overall. In addition, I felt much more comfortable dipping my toes into functional programming practices using this language than a more abstract one like Scala. The added benefit of a strictly typed system and a helpful compiler made me feel much more focused and able to complete parts of my app faster.

Overall, there are a couple things to get used to when it comes to Elm. The first and most important is the architecture. If you plan on doing a project in Elm for the first time, I would read up on that to get an idea of what design considerations Elm expects of you. As I’ve mentioned above, that was what took me the longest to get used to. The other is building decoders - this is not something you have to do in a Rails or even a TypeScript project. Though, depending on how strict your settings are in the latter, you might add types to the responses you receive from an API, which is one step closer to the kind of work put into building an Elm decoder.

I would highly recommend anyone new to strictly typed languages or functional programming to check out Elm as a way to build your first project. I found it a joy to work with and I’m excited to do more with it.

PS: If you want to see the entirety of my project, you can visit the repo. I’ve also created a gist with all of the learning resources I’ve used / currently using. Have fun!