Joseph Kain bio photo

Joseph Kain

Professional Software Engineer learning Elixir.

Twitter LinkedIn Github

What is Idiomatic Elixir?

I think at its core, idiomatic Elixir is what we define it to be. The community, as a collective, defines idiomatic Elixir by writing Elixir code. Idioms are a form of communication, common patterns that we are all familiar with, that we can all recognize.

But, we can only communicate using these patterns if they have wide spread familiarity. To reach this level of communication requires sharing code and reading code written by others.

To this end, I’ve decided to write a book called Idiomatic Elixir. It will be a survey of existing code written by the community, exploring common styles, patterns and techniques.

I’m just getting started with this project, but if you are interested in following the project’s development then I invite you to subscribe to the Idiomatic Elixir mailing list:

If you subscribe you should expect 2-3 emails per month with progress updates and Elixir tips based on what I learn. You will also get 2 free sample chapters from the book be the first to hear when the book launches.

With that out of the way, let’s look at some idiomatic Elixir.

Collectable Idioms

A few weeks ago I wrote about post about the Collectable protocol. I want to continue and look at more examples of how to use Enum.into.

Mapping maps

I’ve mentioned before that Enum.into/3 can be used a lot like like Enum.map/2 in that it can transform data. Below is a great example from Phoenix. The useful thing about this example is that it lets you transform a Map into another Map:

defp filter_values(%{} = map, filter_params) do
  Enum.into map, %{}, fn {k, v} ->
    if is_binary(k) && String.contains?(k, filter_params) do
      {k, "[FILTERED]"}
    else
      {k, filter_values(v, filter_params)}
    end
  end
end

The function used for the transformation solves a specific problem for Phoenix, but that isn’t the purpose of this example. This idiom simplifies down to:

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

This idiom allows you to create a new map with the same keys and transformed values. Of course you can also use a function that remaps the keys, or both the keys and the values.

You can’t use Enum.map/2 to do this because it always returns a list as this test shows:

test "Enum.map over a map returns a list" do
  map = %{a: 1, b: 2}
  result = Enum.map map, fn {k, v} -> {k, v * 2} end
  assert is_list(result)
end

Collecting options

Here’s an example that Phoenix uses twice:

engines =
  @engines
  |> Keyword.merge(raw_config(:template_engines))
  |> Enum.filter(fn {_, v} -> v end)
  |> Enum.into(%{})

This is a useful Elixir idiom for collecting options. It

  • Merges two keyword lists
  • Filters the lists removing any items with nil values
  • Collects the remaining values into a map.

This can be used in a lot of places. One would be in collecting options or parameters to a functions. The merge can be used to merge in the default values.

This example starts with a Keyword list but you could also use a Map for the initial values.

The filter is simple but very useful. The simple function fn {_, v} -> v end simply checks the value for truthiness. So it will reject key / value pairs with nil values. This leaves only the meaningful pairs.

The use of Enum.into/2 here is the most simple case but it’s a good ending to the pipeline as it gives us control over the final type of the conversion. If we ended with Enum.filter/2 we would be left with a Keyword List. This is a good example of using Enumerable types as sources and Collectable types as sinks.

Zip into

Elixir’s regex module uses:

Enum.zip(names, results) |> Enum.into(%{})

to create a map of named captures and captured results. This is a nice way to create maps if you have separate lists (or enumerables) of keys and values.

Here’s a more concrete example of this technique:

test "zip into" do
  keys = [:a, :b, :c]
  values = [1, 2, 3]

  result = Enum.zip(keys, values) |> Enum.into(%{})

  assert result == %{a: 1, b: 2, c: 3}
end

Initialization

Elixir has syntactic sugar for creating types like List and Map using literals like this:

list = [:a, :b, :c]
map  = %{a: 1, b: 2, c: 3}

But, some of the more exotic types don’t have a literal syntax and are harder to initialize. For those that implement Collectable, Enum.into can be used to create data structures like this:

~w(foo bar baz) |> Enum.into(MapSet.new)

This idiom shows up in many places and I’ve taken this specific example from the gettext library. Any list can be used for initialization of the set types.

This idiom, of course, also works for HashDict using a Keyword list as the initializer.

test "initialize HashDict" do
  result = [a: 1, b: 2] |> Enum.into HashDict.new

  assert HashDict.get(result, :a) == 1
  assert HashDict.get(result, :b) == 2
end

Next Steps

In this post we looked at several more examples of Enum.into that show off the usefulness of the Collectable protocol.

Also, I announced my plans to write Idiomatic Elixir and I hope to fill it with examples like those above, on a variety of different Elixir topics. I hope this post has given you a taste of what the book will contain.