Comprehensions

Comprehensions são um ‘syntactic sugar’ (uma forma mais simples de escrever) para realizar loops em Enumerables em Elixir. Nesta lição veremos como podemos fazer iterações e gerar os resultados utilizando comprehensions.

Table of Contents

Básico

Em alguns casos comprehensions podem ser usadas para produzir código mais conciso para fazer iterações com Enum e Stream. Vamos começar olhando para uma comprehension simples e então observar suas várias partes:

iex> list = [1, 2, 3, 4, 5]
iex> for x <- list, do: x*x
[1, 4, 9, 16, 25]

A primeira coisa que observamos é o uso de for e um generator (gerador). O que é um generator? Generators são as expressões x <- [1, 2, 3, 4] encontradas em comprehensions. Eles são responsáveis por gerar o próximo valor.

Para nossa sorte, comprehensions não são limitadas a listas; na verdade elas funcionam com qualquer enumerable:

# Keyword Lists
iex> for {_key, val} <- [one: 1, two: 2, three: 3], do: val
[1, 2, 3]

# Maps
iex> for {k, v} <- %{"a" => "A", "b" => "B"}, do: {k, v}
[{"a", "A"}, {"b", "B"}]

# Binaries
iex> for <<c <- "hello">>, do: <<c>>
["h", "e", "l", "l", "o"]

Como muitas outras coisas em Elixir, generators se apoiam no pattern matching para comparar a entrada definida na variável à esquerda. Caso um match não seja encontrado, o valor é ignorado.

iex> for {:ok, val} <- [ok: "Hello", error: "Unknown", ok: "World"], do: val
["Hello", "World"]

É possível utilizar múltiplos generators, bem como loops aninhados:

iex> list = [1, 2, 3, 4]
iex> for n <- list, times <- 1..n do
...>   String.duplicate("*", times)
...> end
["*", "*", "**", "*", "**", "***", "*", "**", "***", "****"]

Para ilustrar melhor cada iteração do loop, vamos usar IO.puts para mostrar os dois valores gerados:

iex> for n <- list, times <- 1..n, do: IO.puts "#{n} - #{times}"
1 - 1
2 - 1
2 - 2
3 - 1
3 - 2
3 - 3
4 - 1
4 - 2
4 - 3
4 - 4

Comprehensions são syntactic sugar e devem ser utilizadas apenas quando for apropriado.

Filtros

Você pode pensar em filtros como um tipo de guard para comprehensions. Quando um valor filtrado retorna false ou nil ele é excluído da lista final. Vamos iterar por um intervalo e olhar apenas os números pares. Nós vamos usar a função is_even/1 do módulo Integer para checar se um valor é par ou não.

import Integer
iex> for x <- 1..10, is_even(x), do: x
[2, 4, 6, 8, 10]

Assim como os generators, nós podemos usar múltiplos filtros. Vamos expandir nosso intervalo e então filtrar apenas para valores que sejam pares e também divisíveis por 3.

import Integer
iex> for x <- 1..100,
...>   is_even(x),
...>   rem(x, 3) == 0, do: x
[6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]

Usando :into

E se nós quisermos produzir algo que não seja uma lista? Passando a opção :into nós podemos fazer exatamente isso! Como uma regra geral, :into aceita qualquer estrutura que implemente o protocolo Collectable.

Usando :into, vamos criar um mapa de uma lista de palavras-chave.

iex> for {k, v} <- [one: 1, two: 2, three: 3], into: %{}, do: {k, v}
%{one: 1, three: 3, two: 2}

Como bitstrings implementam collectables nós podemos usar comprehensions e :into para criar strings:

iex> for c <- [72, 101, 108, 108, 111], into: "", do: <<c>>
"Hello"

É isso! Comprehensions são um modo fácil de iterar por coleções de maneira concisa.

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!