Elixir by Example

Comprehensions

Edit any cell and run it in your browser Download source

A comprehension (for) maps, filters and combines enumerables in one expression. It returns a list by default.

squares = for n <- 1..5, do: n * n
IO.inspect(squares, label: "squares")
squares: [1, 4, 9, 16, 25]

Extra clauses after the generator are filters.

evens = for n <- 1..10, rem(n, 2) == 0, do: n
IO.inspect(evens, label: "evens")
evens: [2, 4, 6, 8, 10]

Several generators give every combination, like nested loops.

pairs = for x <- [:a, :b], y <- [1, 2], do: {x, y}
IO.inspect(pairs, label: "pairs")
pairs: [a: 1, a: 2, b: 1, b: 2]

The left side of <- is a pattern. Elements that don't match are skipped silently, which makes comprehensions handy for picking out results.

results = [{:ok, 1}, {:error, :timeout}, {:ok, 3}]
oks = for {:ok, value} <- results, do: value
IO.inspect(oks, label: "oks")
oks: [1, 3]

Maps enumerate as {key, value} tuples.

stock = %{apple: 0, pear: 4, fig: 2}
in_stock = for {fruit, n} <- stock, n > 0, do: fruit
IO.inspect(Enum.sort(in_stock), label: "in stock")
in stock: [:fig, :pear]

into: collects into something other than a list, such as a map or a string. Maps have no order, and how they print can differ between runtimes (this very example printed b first in the browser), so read keys explicitly instead of relying on output.

upcased = for {k, v} <- %{a: "x", b: "y"}, into: %{}, do: {k, String.upcase(v)}
IO.inspect(upcased.a, label: "into map, :a")
IO.inspect(Enum.sort(upcased), label: "sorted pairs")
into map, :a: "X"
sorted pairs: [a: "X", b: "Y"]

Bitstring generators walk binaries byte by byte.

bytes = for <<b <- "hi!">>, do: b
IO.inspect(bytes, label: "bytes")
bytes: ~c"hi!"

uniq: true drops duplicates, and reduce: turns a comprehension into a fold with an accumulator.

IO.inspect(for(x <- [1, 1, 2, 3, 3], uniq: true, do: x * 10), label: "uniq")

total =
  for n <- 1..4, reduce: 0 do
    acc -> acc + n
  end

IO.inspect(total, label: "reduce")
uniq: [10, 20, 30]
reduce: 10

Pitfall: a comprehension doesn't update outer variables. Writing count = 0 and then for x <- list do count = count + 1 end leaves count at 0 (the compiler warns that the inner variable is unused). Use reduce: or Enum.reduce/3 to carry state.

Pitfall: the silent skip on pattern mismatch can hide bugs: for {:ok, v} <- results drops errors without a trace. Match errors explicitly when you need to know about them.