Elixir Prelude: Packing up utility functions

August 21, 2015, [MD]

Introduction

I've been spending the summer in China, head-deep in Elixir, writing interactive scripts for an EdX MOOC on inquiry and technology for teachers (screenshots). I will have much more to share about that project later, but now I wanted to share a small Elixir package I released on Github called Prelude (documentation). This is a collection of utility functions for Elixir that I've extracted out of my code base.

Organization

I began by scattering these utility functions all over as small defps, but when I wanted to reuse them in other modules, I began gathering them. Initially I had a single file called Prelude - modelled after the Haskell Prelude, just because it was the only file I'd ever wholesale import into my other modules.

Eventually I began separating the code out into sub-modules, which meant I went from

import Prelude

map_atomify(...)

to

Prelude.Map.atomify(...)

which seems cleaner.

The point of gathering "trivial" functions

Many of these functions are very simple, but yet things you use all the time - gathering them together both means less repetition and greater legibility. Compare:

x
|> Enum.map(fn {k, v} -> {v, k} end)
|> Enum.into(%{})

to

x
|> Prelude.Map.switch

The code in the first version is short enough that I wouldn't bother making it a separate function, but the intent of the second snippet is much clearer. I might also forget to do Enum.into(%{}), and somehow I always forget the end in anonymous functions. So, some quick time saving.

Map functions

Some of the functions are a bit more involved, mainly found in the Prelude.Map module. I was inspired by the way Clojurists often work with deeply nested maps, sometimes keeping the entire application state in one atom. This has also become relevant in Javascript-land with experiments around immutable state such as Baobab.

I see deeply nested maps much less often in Elixir. Part of it might be the architecture (we use gen_servers, message passing, and ETS tables, rather than atom-swapping), but part of it might also be because of the syntax (%{%{%{}}} is not pretty).

However, Elixir already has get_in and put_in to reach into deeply nested objects. Prelude provides deep_put, which is a parallel to mkdir -p, it will insert an object and create any part of the missing path. If you put to a path that already exists, it will turn it into an array.

Built on top of deep_put is group_by. Building the Phoenix app, I often had to extract data from the database using Ecto, and render it in templates. Let's say you have a table of students, listing their group affiliation, and which class their in. You wish to list all students, sorted by class, and then by group. With group_by, you can get the data from Ecto like this:

MyRepo.all(Student)
|> Prelude.Map.group_by([:class, :group])

The result will be a map of classes. Each class will point to a map of groups, and each group will point to a list of students in that group. Then in an EEx template, you can display the list like this:

<%= for class <- @classes %>
  <h2><%= class %></h2>
  <%= for group <- class %>
    <h3><%= group %></hr>
    <%= for student <- group %>
      <li><%= student %></li>
    <% end %>
  <% end %>
<% end %>

Wrapping up

Anyway, the code is available, so feel free to browse around. I will be using this in my future projects, and probably adding functionality that is general enough to stand on its own. I'd love to add tests, and am happy to think about naming, APIs etc, if anyone else think this is interesting.

It's also my first time to use ExDoc and Github Pages, but both were a breeze.

Stian HĂ„klev August 21, 2015 Toronto
comments powered by Disqus