Elm: counting groups of items in a list

So. Elm. It’s been an interesting experience for me, coming from a procedural language (JS) background. The learning curve is steep, but the functional nature of Elm, along with its compile time type safety really pays off. One of the (few!) problems I’ve found as a newcomer however, is that the documentation can be really frustrating sometimes. In this post, I hope to remedy that slightly by providing a newcomer’s perspective on a little bit of data processing in Elm.

Let’s say I’ve collected a list of tags from questions on StackOverflow. I want to create a unique dictionary of tags and the number of times they occur in the dataset. To do that, we’ll need to process the data from a flat list into a Dict of key/value pairs. The key will be the name of the tag, and the value will be the number of occurrences in the dataset.

This will store the list of tags under model.tagList, which we can use in the view to render a (not so) pretty list:

view:Model->Htmlmsgviewmodel=div[][section[][text"All the tags",ul[](List.map(\tag->li[][texttag])model.tagList)]]

The entire program so far is shown below. It’s a pretty standard Elm boilerplate app. The most interesting bits are described above; how we store the list, and how we present it.

Great, we’ve got a working Elm program. Next, I’ll go into a bit of data processing to turn this flat, boring list into a Dict of tags and counts.

Dicts

As a first step towards grouping the data, we need to start using a Dict. Dicts contain unique keys with an associated value. They’re the same as Map()s in JavaScript. As a first step we’ll just render a list of unique tags to keep things understandable. The counts will come later.

First, the model type needs to change to use an Elm Dict for our list of tags:

importDictexposing(..)typealiasModel={tagList:DictStringInt}

tagList is now of type Dict String Int, which is a map of String keys to Int values. This will hold the tag -> count mapping.

We need to write a function to transform the list of tags when the model is initialised, so let’s write that:

This function will foldr (Array.reduce() in JavaScript parlance) the tag list and create a Dict. The keys will be the unique list of tags from the input, while the values at the moment will all be 0 for simplicity’s sake.

You can see that using the pipeline operator makes things much easier to read. Here’s a demo:

Counting keys

This Dict isn’t very useful without some actual data in its keys. To fix that, we need to update groupTags to actually count the number of occurrences instead of just setting each value to 0. Here’s what it looks like:

Instead of overwriting existing keys with Dict.insert as before, we’re now using Dict.update which takes three arguments:

tag – they Dict key to search for

updateFunc – how to update the Dict

dictToUpdate – the starting Dict we want to update

It’s important to note that Dict.update will upsert a key; if it doesn’t exist, it’ll get created. Dict.update returns a whole new Dict, with the updated/inserted key/value pair. This is where the second argument (updateFunc) comes into play. It is supplied with one argument, existingCount, which is a Maybe type. If there’s no existing key, this will be Nothing, otherwise you’ll get Just <dict value type>. In our case, this would be Just Int. The case statement will add one to an existing value, or insert a new key into the dict with a starting value of 1.

Because this code is looping through a list of tuples, we need to use Tuple.first and Tuple.second to extract the tag and count respectively. The last bit is to call toString on the count to turn the Int into a String, ready for outputting in HTML.

Now we’ve got something that looks like this:

And we’re done! Well done if you made it down here.

Wrapping up

Hopefully I’ve helped you understand a bit about how data processing (particularly with Dicts) works in Elm with some practical code. During my Elm learning experience, I found there was a gap between absolute beginner tutorials and more advanced stuff. Perhaps that’s my procedural background talking, or perhaps I just need to be smarter. Who knows, but either way the aim of this article was to help bridge this gap. Let me know on Twitter if there’s something I can do to improve this article, and as always, thanks for read’n.