Today I'd like to introduce another piece of syntax to you:
for
comprehensions. You can think of them like high-powered enumeration helpers.
Let's get started.
Project
We'll start a new project to play with these:
mix new for_playground
cd for_playground
vim test/for_playground_test.exs
We'll use the test suite to help us play.
for
is the Special Form that lets you play with various comprehensions. The
most common are List Comprehensions. Comprehensions let you handle combinations
of things. They look like this:
defmodule ForPlaygroundTest do
use ExUnit.Case
test "basic for comprehensions" do
result = for i <- [1, 2, 3], do: i
assert [1, 2, 3] == result
end
end
By the way, it's worth mentioning that that in-line do is always possible as a
replacement for a
do/end
block in an Elixir expression. This is equivalent:
defmodule ForPlaygroundTest do
# ...
test "basic for comprehensions" do
result =
for i <- [1, 2, 3] do
i
end
assert [1, 2, 3] == result
end
end
The thing on the right of the leftward-facing arrow is known as a "generator". Here, our generator is a list.
We can use
for
comprehensions to combine items. For instance, let's describe a
deck of cards:
defmodule ForPlaygroundTest do
# ...
test "generating a deck of cards" do
suits = [:clubs, :diamonds, :hearts, :spades]
ranks = [2, 3, 4, 5, 6, 7, 8, 9, 10, :jack, :queen, :king, :ace]
# I'll paste in the list of all cards, to compare against
all_cards =
[
{2, :clubs},
{3, :clubs},
{4, :clubs},
{5, :clubs},
{6, :clubs},
{7, :clubs},
{8, :clubs},
{9, :clubs},
{10, :clubs},
{:jack, :clubs},
{:queen, :clubs},
{:king, :clubs},
{:ace, :clubs},
{2, :diamonds},
{3, :diamonds},
{4, :diamonds},
{5, :diamonds},
{6, :diamonds},
{7, :diamonds},
{8, :diamonds},
{9, :diamonds},
{10, :diamonds},
{:jack, :diamonds},
{:queen, :diamonds},
{:king, :diamonds},
{:ace, :diamonds},
{2, :hearts},
{3, :hearts},
{4, :hearts},
{5, :hearts},
{6, :hearts},
{7, :hearts},
{8, :hearts},
{9, :hearts},
{10, :hearts},
{:jack, :hearts},
{:queen, :hearts},
{:king, :hearts},
{:ace, :hearts},
{2, :spades},
{3, :spades},
{4, :spades},
{5, :spades},
{6, :spades},
{7, :spades},
{8, :spades},
{9, :spades},
{10, :spades},
{:jack, :spades},
{:queen, :spades},
{:king, :spades},
{:ace, :spades}
]
# Then we use a for comprehension to combine the suits and ranks
result =
for suit <- suits,
rank <- ranks,
do: {rank, suit}
assert all_cards == result
end
end
You an also add filters to your comprehensions, to restrict values from appearing in the results. We'll use a range of integers as our generator, keeping only the even ones:
test "filters in for comprehensions" do
result =
for i <- 0..10, rem(i, 2) == 0 do
i
end
assert result == [0, 2, 4, 6, 8, 10]
end
So far, each of our comprehensions has produced a list. We can collect the values into different structures as well. Let's collect them into a map instead of a list:
test "collecting results into a map" do
result =
# We'll use the `into` option to specify a different term to collect into
for i <- 0..2, into: %{} do
# When collecting into maps, you're expected to return a 2-tuple with
# the key and value.
{"#{i}", i}
end
assert result == %{"0" => 0, "1" => 1, "2" => 2}
end
The
into
feature is supported by
the
Collectable
protocol
.
To learn more about comprehensions, you can check out
the guide on the Elixir
site
or
the
documentation for the
for
special
form
.
Summary
In today's episode we saw how to use comprehensions in Elixir, both for enumerating simple collections as well as combining and filtering them. I hope you have fun playing with enumerables using comprehensions. See you soon!
Comments for
forComprehensions