In Episode 010 we covered List Comprehensions. In Elixir 0.13, they've been revamped, and now they're just more generally named 'Comprehensions', as they work with more than just lists using a uniform syntax.

From the updated Elixir documentation, "Comprehensions allow you to quickly build a data structure from an enumerable or a bitstring."

Let's look at how the new style comprehensions work.

Project

We'll be exploring the comprehensions in iex, so fire up a console:

iex

Now, in their most basic form, list comprehensions looks like this:

for n <- [1,2,3,4], do: n*2

You can use this either to collect the output, or as a poor man's way to repeat a task a few times:

for _ <- [1,2,3], do: IO.puts "zomg"

Now, in the comprehension special form, that leftward-facing arrow construct is referred to as a generator. The left-hand side is the locally bound variable for each loop, and the right-hand side is the enumerable that will be fed into it.

As you'd expect, you can have more than one generator:

for x <- [1, 2, 3], y <- [:a, :b, :c], do: {x, y}

You can also add a filter to the form:

for x <- [1, 2, 3, 4],
    y <- [:a, :b, :c],
    rem(x, 2) == 0,
    do: {x, y}

Of course, you can use comprehensions with anything enumerable as a generator input, not just a list. This means we can use a comprehension to map over a file stream:

for x <- File.stream!("/usr/share/dict/words"), do: String.upcase(x)

Each of the expressions we've run so far has output a list. This isn't always what you want, so you can use the :into option to get something else out. For instance, a map:

# NOTE: This expression takes a while to evaluate, because large maps are slow
# in Erlang until the first point release of R17
for x <- File.stream!("/usr/share/dict/words"),
    String.first(x) == "b",
    into: %{},
    do: {x, String.upcase(x)}

You can also use this syntax with binaries, although since they aren't enumerable there's a slight modification - essentially, you wrap the generator in a binary, if that makes sense:

string = <<"something">>
for <<x <- string>>, do: x+1

Various of these features can be combined to do particularly neat things. For example, from the Elixir docs, here's an upcasing echo server using the new IO.stream as both a generator and an into option:

for line <- IO.stream(:stdio, :line), into: IO.stream(:stdio, :line) do
  String.upcase(line)
end

Summary

In today's episode, we quickly surveyed the new Comprehensions feature. It's more full-featured than older style list comprehensions were, and the plan is for it to be easily extended since now any structure will be able to be used in the :into option, as long as it implements the appropriate protocol. See you soon!