Este artigo não é uma leitura passiva. É um treino.
Antes de encarar o tutorial "Construindo um Índice Invertido em Elixir", você precisa ter memória muscular em um conjunto específico de funções e conceitos. Ler uma vez não basta — é preciso repetir, errar, ver o erro, corrigir e repetir de novo.
Cada seção tem a mesma estrutura:
- Conceito — explicação curta.
- Exemplos — o que funciona.
- Contra-exemplos — o que quebra e por quê.
- Equívocos comuns — o que quase todo mundo pensa errado.
- Exercícios — repita até virar automático.
- Gabarito — confira e siga.
Abra o IEx (iex) e digite tudo. Sério. Digitar é parte do treino.
1. O operador pipe |>
Conceito
|> passa o resultado da esquerda como primeiro argumento da função à direita.
Exemplos
iex> "ola" |> String.upcase()
"OLA"
iex> "ola" |> String.upcase() |> String.reverse()
"ALO"
Contra-exemplos
O pipe só passa como primeiro argumento. Se a função precisa do valor em outra posição, o pipe não serve sozinho:
# ❌ Errado — String.replace/3 espera o alvo primeiro, depois o padrão
iex> "ola mundo" |> String.replace("mundo", "elixir")
** (ArgumentError) ...
# ✅ Correto — String.replace(alvo, padrao, substituto)
iex> String.replace("ola mundo", "mundo", "elixir")
"ola elixir"
# ✅ Ou usando pipe corretamente
iex> "ola mundo" |> String.replace("mundo", "elixir")
"ola elixir"
Equívocos comuns
-
"O pipe faz algo mágico." Não faz. É só reescrita.
a |> f()é literalmentef(a). -
"Posso usar pipe com qualquer coisa." Só com o resultado de uma expressão à esquerda.
1 + 2 |> IO.puts()não funciona como você imagina — veja abaixo.
# ❌ Pega o resultado de "2 |> IO.puts()" e soma com 1
iex> 1 + 2 |> IO.puts()
2
** (ArithmeticError) ...
# ✅ Parênteses forçam a ordem certa
iex> (1 + 2) |> IO.puts()
3
:ok
Exercícios
Sem rodar no IEx, preveja o resultado. Depois confira.
# 1
"abc" |> String.upcase() |> String.length()
# 2
"abc" |> String.length() |> String.upcase()
# 3
String.upcase("abc") |> String.reverse()
# 4
"abc" |> String.reverse() |> String.upcase() |> String.slice(0, 2)
Gabarito:
3- Erro —
String.upcase/1espera string, masString.length/1devolve número. "CBA""CB"
2. String.downcase/1
Conceito
Converte toda a string para minúsculas. Lida com Unicode (acentos) por padrão.
Exemplos
iex> String.downcase("GATO")
"gato"
iex> String.downcase("Gato Subiu No Telhado")
"gato subiu no telhado"
iex> String.downcase("AÇÃO")
"ação"
Contra-exemplos
# ❌ Não funciona com número
iex> String.downcase(42)
** (FunctionClauseError) ...
# ❌ Não funciona com nil
iex> String.downcase(nil)
** (FunctionClauseError) ...
Equívocos comuns
-
"Downcase não mexe em acentos." Mexe, sim — e corretamente.
"AÇÃO"vira"ação", não"aÇÃo"nem nada esquisito. - "Downcase altera o tamanho da string." Em geral não, mas alguns caracteres Unicode especiais podem mudar. Em 99% dos casos, o tamanho é o mesmo.
Exercícios
Preveja e depois confirme:
# 5
String.downcase("HELLO World")
# 6
String.downcase("") == ""
# 7
String.downcase("Ç") == "ç"
# 8
String.length(String.downcase("ÉÀÇ"))
Gabarito:
"hello world"truetrue3
3. String.replace/3
Conceito
Substitui todas as ocorrências de um padrão (string ou regex) por um substituto.
Exemplos
iex> String.replace("bom dia", "dia", "noite")
"bom noite"
iex> String.replace("a-a-a", "a", "b")
"b-b-b"
iex> String.replace("a1b2c3", ~r/\d/, "*")
"a*b*c*"
Contra-exemplos
# ❌ Substituindo por string vazia, sem pensar
iex> String.replace("fim.início", ".", "")
"fimíncio"
Isso é o exemplo clássico do tutorial. Vamos repetir porque é o ponto onde mais gente erra.
# ✅ Substituindo por espaço
iex> String.replace("fim.início", ".", " ")
"fim início"
# ❌ Agora com vários separadores
iex> String.replace("a,b;c.d", ~r/[,;.]/, "")
"abcd"
# ✅ Com espaço separa corretamente
iex> String.replace("a,b;c.d", ~r/[,;.]/, " ")
"a b c d"
Equívocos comuns
-
"Substituir por
""é igual a substituir por" "." Não é.""apaga;" "separa. - "replace só troca uma ocorrência." Troca todas, sempre.
-
"replace ignora maiúsculas." Não ignora.
"A"e"a"são diferentes para ele.
Exercícios
# 9
String.replace("banana", "a", "o")
# 10
String.replace("a b c", " ", "")
# 11
String.replace("a b c", " ", "_")
# 12
String.replace("cpf: 123.456.789-00", ~r/\D/, "")
# 13
String.replace("abacaxi", "a", "")
Gabarito:
"bonono""abc""a_b_c""12345678900""bcxi"
4. String.split/2 e String.split/3
Conceito
Divide uma string em lista, usando um separador (string ou regex). O terceiro argumento aceita opções — a mais usada é trim: true, que descarta strings vazias nas pontas.
Exemplos
iex> String.split("gato cachorro")
["gato", "cachorro"]
iex> String.split("a,b,c", ",")
["a", "b", "c"]
iex> String.split("gato cachorro", ~r/\s+/)
["gato", "cachorro"]
Contra-exemplos
# ❌ split com espaço único não trata espaços múltiplos
iex> String.split("gato cachorro", " ")
["gato", "", "", "cachorro"]
# ✅ regex com + trata como um separador só
iex> String.split("gato cachorro", ~r/\s+/)
["gato", "cachorro"]
Equívocos comuns
-
"
trim: trueremove vazios do meio." Não remove. Só das pontas. - "Sem trim, split remove vazios automaticamente." Não remove nada.
iex> String.split(" a b ", " ")
["", "", "a", "", "b", "", ""]
iex> String.split(" a b ", " ", trim: true)
["a", "", "b"]
Repare: com trim, os vazios das pontas somem, mas os do meio ficam. Para eliminá-los, use Enum.reject/2 ou um regex com +.
Exercícios
# 14
String.split("a b c")
# 15
String.split("a,b,,c", ",")
# 16
String.split("a,b,,c", ",", trim: true)
# 17
String.split("a b c", ~r/\s+/)
# 18
String.split("a b c", " ")
Gabarito:
["a", "b", "c"]["a", "b", "", "c"]-
["a", "b", "", "c"]— trim só tira das pontas ["a", "b", "c"]["a", "", "", "b", "", "", "c"]
5. Sigil ~w (word list)
Conceito
Cria uma lista de strings a partir de palavras separadas por espaço.
iex> ~w(a o de para)
["a", "o", "de", "para"]
Contra-exemplos
O ~w separa por espaço, não por vírgula:
# ❌ Vírgulas viram parte do token
iex> ~w(a, o, de)
["a,", "o,", "de"]
# ✅ Sem vírgulas
iex> ~w(a o de)
["a", "o", "de"]
Equívocos comuns
- "Posso usar vírgula." Não. Só espaço.
-
"~w cria palavras, não strings." Cria strings. Se quiser átomos, use
~w(...)a. - "~w sempre cria lista de strings." Sim, por padrão.
iex> ~w(a b c)
["a", "b", "c"]
iex> ~w(a b c)a
[:a, :b, :c]
iex> ~w(1 2 3)
["1", "2", "3"]
iex> ~w(1 2 3)a
[:"1", :"2", :"3"]
Exercícios
# 19
~w(gato cachorro peixe) == ["gato", "cachorro", "peixe"]
# 20
~w(a b c) == ~w(c b a)
# 21
length(~w(a a a a))
# 22
"gato" in ~w(gato cachorro)
Gabarito:
true-
false— ordem importa -
4— repetições contam true
6. Regex essencial e a flag /u
Conceito
Regex em Elixir usa o sigil ~r. A flag /u ativa o modo Unicode — obrigatória para usar \p{L} e \p{N}.
Exemplos
iex> "gato123" =~ ~r/\d/
true
iex> "gato123" =~ ~r/\p{L}+/u
true
iex> "123" =~ ~r/\p{L}/u
false
iex> "é" =~ ~r/\p{L}/u
true
Contra-exemplos
Sem /u, classes Unicode não funcionam como esperado:
# ❌ \p{L} sem /u em alguns casos quebra
iex> "gato" =~ ~r/\p{L}/
true # funciona por acaso em ASCII
iex> "é" =~ ~r/\p{L}/
** (Regex.CompileError) ...
Regra: sempre que usar \p{...}, adicione /u.
A classe negada [^...]
iex> "olá" =~ ~r/[^\p{L}\p{N}\s]/u
false # só tem letras
iex> "olá!" =~ ~r/[^\p{L}\p{N}\s]/u
true # tem exclamação
iex> "a b" =~ ~r/[^\p{L}\p{N}\s]/u
false # espaço é permitido
iex> "a-b" =~ ~r/[^\p{L}\p{N}\s]/u
true # hífen não é letra, número nem espaço
Equívocos comuns
-
"
\p{L}e[a-z]são a mesma coisa." Não são.\p{L}cobre todos os alfabetos Unicode. -
"Sem
/ufunciona igual." Em ASCII simples às vezes funciona; com acentos, quebra. -
"
^dentro de[^...]é o início da string." Não. Dentro de[...],^nega a classe.
Exercícios
Preveja true ou false:
# 23
"abc" =~ ~r/[^\p{L}\p{N}\s]/u
# 24
"abc!" =~ ~r/[^\p{L}\p{N}\s]/u
# 25
"123" =~ ~r/[^\p{L}\p{N}\s]/u
# 26
"a b" =~ ~r/[^\p{L}\p{N}\s]/u
# 27
"a.b" =~ ~r/[^\p{L}\p{N}\s]/u
# 28
"" =~ ~r/[^\p{L}\p{N}\s]/u
# 29
"a b" =~ ~r/[^\p{L}\p{N}\s]/u
Gabarito:
-
false— só letras -
true— exclamação -
false— só dígitos -
false— espaço é permitido por\s -
true— ponto -
false— vazio não casa com nada -
false— dois espaços ainda são\s
7. Enum.map/2
Conceito
Transforma cada elemento da lista com uma função. O tamanho da lista é preservado.
iex> Enum.map([1, 2, 3], fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.map(["a", "b"], fn s -> String.upcase(s) end)
["A", "B"]
Contra-exemplos
# ❌ map não achata
iex> Enum.map([[1, 2], [3, 4]], fn l -> l end)
[[1, 2], [3, 4]]
# ❌ map não filtra
iex> Enum.map([1, 2, 3, 4], fn x -> x > 2 end)
[false, false, true, true]
Equívocos comuns
- "map remove elementos." Não remove. Um vira um.
-
"map achata listas aninhadas." Não achata. Isso é trabalho do
flat_map.
Exercícios
# 30
Enum.map([1, 2, 3], fn x -> x + 1 end)
# 31
Enum.map(["a", "b"], fn s -> s <> s end)
# 32
Enum.map([1, 2, 3], fn x -> x > 1 end)
# 33
Enum.map([[1], [2, 3]], fn l -> length(l) end)
# 34
Enum.map([], fn x -> x end)
Gabarito:
[2, 3, 4]["aa", "bb"][false, true, true][1, 2][]
8. Enum.flat_map/2
Conceito
Como map, mas achata um nível. Se sua função retorna listas, o resultado final é uma lista única.
iex> Enum.flat_map([[1, 2], [3, 4]], fn l -> l end)
[1, 2, 3, 4]
iex> Enum.flat_map(["a b", "c d"], fn s -> String.split(s) end)
["a", "b", "c", "d"]
Contra-exemplos
# ❌ flat_map não achata tudo, só um nível
iex> Enum.flat_map([[[1]], [[2]]], fn l -> l end)
[[1], [2]]
# ❌ flat_map com função que devolve não-lista
iex> Enum.flat_map([1, 2], fn x -> x * 2 end)
[2, 4] # funciona porque map também aceita
Equívocos comuns
- "flat_map achata recursivamente." Não. Um nível só.
- "flat_map é sempre melhor que map." Não. Use quando sua função produz listas e você quer unificá-las.
Exercícios
# 35
Enum.flat_map([1, 2, 3], fn x -> [x, x] end)
# 36
Enum.flat_map(["ab", "cd"], fn s -> String.graphemes(s) end)
# 37
Enum.flat_map([1, 2], fn x -> [] end)
# 38
Enum.flat_map([1, 2], fn x -> [[x]] end)
Gabarito:
[1, 1, 2, 2, 3, 3]["a", "b", "c", "d"][]-
[[1], [2]]— só um nível de achatamento
9. Enum.filter/2 e Enum.reject/2
Conceito
filter mantém os elementos que passam na condição. reject descarta esses. São opostos.
iex> Enum.filter([1, 2, 3, 4], fn x -> x > 2 end)
[3, 4]
iex> Enum.reject([1, 2, 3, 4], fn x -> x > 2 end)
[1, 2]
Contra-exemplos
# ❌ filter não transforma
iex> Enum.filter([1, 2, 3, 4], fn x -> x > 2 end) |> Enum.map(fn x -> x * 10 end)
[30, 40]
# precisa mapear depois se quiser transformar
# ❌ reject com condição invertida dá o mesmo resultado de filter
iex> Enum.reject([1, 2, 3, 4], fn x -> x <= 2 end)
[3, 4]
Equívocos comuns
-
"filter e reject são iguais com condição negada." São — mas escrever
reject(&(&1 in stopwords))é mais direto quefilter(fn x -> x not in stopwords end). - "filter preserva a ordem." Preserva.
- "filter pode duplicar." Não pode.
Exercícios
# 39
Enum.filter([1, 2, 3], fn x -> x > 1 end)
# 40
Enum.reject([1, 2, 3], fn x -> x > 1 end)
# 41
Enum.filter(~w(a o gato de), fn t -> t in ~w(a o de) end)
# 42
Enum.reject(~w(a o gato de), fn t -> t in ~w(a o de) end)
# 43
Enum.filter([1, 2, 3], fn _ -> true end)
# 44
Enum.reject([1, 2, 3], fn _ -> false end)
Gabarito:
[2, 3][1]["a", "o", "de"]["gato"][1, 2, 3][1, 2, 3]
10. Enum.uniq/1
Conceito
Remove duplicatas mantendo a primeira ocorrência.
iex> Enum.uniq([1, 2, 2, 3, 1, 4])
[1, 2, 3, 4]
iex> Enum.uniq(["a", "b", "a"])
["a", "b"]
Contra-exemplos
# ❌ uniq não ordena
iex> Enum.uniq([3, 1, 2, 1, 3])
[3, 1, 2]
# ✅ Para ordenar, precisa combinar
iex> [3, 1, 2, 1, 3] |> Enum.uniq() |> Enum.sort()
[1, 2, 3]
Equívocos comuns
- "uniq ordena." Não ordena. Só remove duplicatas.
- "uniq preserva a última." Preserva a primeira.
-
"uniq compara com
==." Compara com===(mais estrito).
Exercícios
# 45
Enum.uniq([1, 1, 1])
# 46
Enum.uniq([3, 1, 3, 2, 1])
# 47
Enum.uniq(["a", "A", "a"])
# 48
[1, 2, 2, 3] |> Enum.uniq() |> Enum.sort()
Gabarito:
[1][3, 1, 2]-
["a", "A"]— Elixir diferencia maiúsculas e minúsculas [1, 2, 3]
11. Enum.sort/1 e Enum.sort_by/2
Conceito
sort/1 ordena valores. sort_by/2 ordena por um critério extraído por uma função.
iex> Enum.sort([3, 1, 2])
[1, 2, 3]
iex> Enum.sort_by([{1, "b"}, {2, "a"}], fn {n, _} -> n end)
[{1, "b"}, {2, "a"}]
iex> Enum.sort_by([{1, "b"}, {2, "a"}], fn {_, l} -> l end)
[{2, "a"}, {1, "b"}]
Contra-exemplos
sort/1 não consegue ordenar coisas incomparáveis:
iex> Enum.sort([1, "a", :ok])
** (ArgumentError) ...
E sort/1 de strings é lexicográfico — maiúsculas antes de minúsculas:
iex> Enum.sort(["banana", "Abacaxi", "uva"])
["Abacaxi", "banana", "uva"]
Se quiser ignorar maiúsculas, normalize antes.
Equívocos comuns
-
"sort ordena por qualquer critério." Só ordena valores comparáveis. Para critério, use
sort_by. -
"sort_by aceita função com
&1só." Aceita qualquer função, capture incluído. -
"Para inverter, uso reverse depois." Funciona, mas
sort_by(fn x -> -x end)ousort_by(fn x -> x end, :desc)é mais direto.
Exercícios
# 49
Enum.sort([3, 1, 2])
# 50
Enum.sort(["c", "a", "b"])
# 51
Enum.sort_by([3, 1, 2], fn x -> -x end)
# 52
Enum.sort_by(["banana", "uva", "abacaxi"], fn s -> String.length(s) end)
# 53
Enum.sort_by([{2, "a"}, {1, "b"}], fn {n, _} -> n end)
Gabarito:
[1, 2, 3]["a", "b", "c"][3, 2, 1]["uva", "banana", "abacaxi"][{1, "b"}, {2, "a"}]
12. Enum.group_by/2 e Enum.group_by/3
Conceito
Agrupa elementos por uma chave, retornando um mapa. A versão /3 permite também transformar cada elemento antes de agrupar.
iex> Enum.group_by([1, 2, 3, 4], fn x -> rem(x, 2) end)
%{0 => [2, 4], 1 => [1, 3]}
iex> Enum.group_by([1, 2, 3, 4], fn x -> rem(x, 2) end, fn x -> x * 10 end)
%{0 => [20, 40], 1 => [10, 30]}
Contra-exemplos
# ❌ group_by não transforma as chaves automaticamente
iex> Enum.group_by(["gato", "GATO"], fn s -> s end)
%{"gato" => ["gato"], "GATO" => ["GATO"]}
# ✅ Normalize antes de agrupar
iex> Enum.group_by(["gato", "GATO"], fn s -> String.downcase(s) end)
%{"gato" => ["gato", "GATO"]}
Equívocos comuns
- "group_by ordena." Não ordena. Preserva a ordem dentro de cada grupo.
- "group_by com função de chave e valor são iguais." Não. A 2ª função só é aplicada aos valores — a chave é fixa.
Exercícios
# 54
Enum.group_by([1, 2, 3, 4, 5], fn x -> rem(x, 3) end)
# 55
Enum.group_by(["a", "bb", "c", "dd"], fn s -> String.length(s) end)
# 56
Enum.group_by(["a", "bb", "c"], fn s -> String.length(s) end, fn s -> String.upcase(s) end)
# 57
Enum.group_by(~w(gato gato cachorro), fn t -> t end)
Gabarito:
%{0 => [3], 1 => [1, 4], 2 => [2, 5]}%{1 => ["a", "c"], 2 => ["bb", "dd"]}%{1 => ["A", "C"], 2 => ["BB"]}%{"gato" => ["gato", "gato"], "cachorro" => ["cachorro"]}
13. Enum.reduce/3
Conceito
Percorre a coleção acumulando um valor. Recebe elemento e acumulador atual, devolve o novo acumulador.
iex> Enum.reduce([1, 2, 3, 4], 0, fn x, acc -> acc + x end)
10
Contra-exemplos
# ❌ Esquecer o valor inicial
iex> Enum.reduce([1, 2, 3], fn x, acc -> acc + x end)
** (FunctionClauseError) ... # ou pega o primeiro como acc
# ❌ Devolver o elemento em vez do acumulador
iex> Enum.reduce([1, 2, 3], 0, fn x, acc -> x end)
3
Equívocos comuns
-
"reduce é sempre o mais indicado." Não.
map,filter,sumcobrem a maioria dos casos com mais clareza. -
"Preciso do valor inicial sempre." Na verdade
reduce/2(sem inicial) existe, mas recomendo evitar — pega o primeiro elemento como acumulador e confunde. Sempre passe o inicial. -
"reduce só soma." Soma é só o exemplo mais simples.
reduceconstrói listas, mapas, qualquer coisa.
Exercícios
# 58
Enum.reduce([1, 2, 3], 0, fn x, acc -> acc + x end)
# 59
Enum.reduce([1, 2, 3], 1, fn x, acc -> acc * x end)
# 60
Enum.reduce(["a", "b", "c"], "", fn s, acc -> acc <> s end)
# 61
Enum.reduce([1, 2, 3, 4], [], fn x, acc -> if rem(x, 2) == 0, do: acc ++ [x], else: acc end)
# 62
Enum.reduce(["a", "b", "c"], %{}, fn s, acc -> Map.put(acc, s, String.length(s)) end)
Gabarito:
66"abc"[2, 4]%{"a" => 1, "b" => 1, "c" => 1}
14. Enum.with_index/1
Conceito
Transforma cada elemento em uma tupla {elemento, índice} começando em 0.
iex> Enum.with_index(["a", "b", "c"])
[{"a", 0}, {"b", 1}, {"c", 2}]
Contra-exemplos
with_index não aceita índice inicial customizado na versão padrão:
# ❌ Não existe with_index/2 na forma que você imagina
iex> Enum.with_index(["a", "b"], 1)
** (UndefinedFunctionError) ...
A assinatura alternativa recebe uma função:
iex> Enum.with_index(["a", "b"], fn el, i -> {el, i + 100} end)
[{"a", 100}, {"b", 101}]
Equívocos comuns
- "O índice começa em 1." Começa em 0.
- "with_index altera a ordem." Não altera.
Exercícios
# 63
Enum.with_index(["x", "y"])
# 64
Enum.with_index([10, 20, 30])
# 65
Enum.with_index(["a", "b"], fn el, i -> {el, i * 2} end)
# 66
[10, 20, 30] |> Enum.with_index() |> Enum.map(fn {v, _} -> v end)
Gabarito:
[{"x", 0}, {"y", 1}][{10, 0}, {20, 1}, {30, 2}][{"a", 0}, {"b", 2}][10, 20, 30]
15. Map.new/1 e Map.new/2
Conceito
Cria um mapa a partir de uma lista de tuplas {chave, valor}. A versão /2 aplica uma função antes.
iex> Map.new([{:a, 1}, {:b, 2}])
%{a: 1, b: 2}
iex> Map.new([1, 2, 3], fn x -> {x, x * x} end)
%{1 => 1, 2 => 4, 3 => 9}
Contra-exemplos
# ❌ Lista sem tuplas de 2 elementos
iex> Map.new([1, 2, 3])
** (ArgumentError) ...
# ❌ Duplicatas — a última vence
iex> Map.new([{:a, 1}, {:a, 2}])
%{a: 2}
Equívocos comuns
- "Map.new preserva ordem." Mapas em Elixir não têm ordem definida para as chaves.
- "Duplicatas dão erro." Não. A última sobrescreve.
- "Map.new aceita qualquer lista." Precisa ser lista de tuplas de 2 elementos.
Exercícios
# 67
Map.new([{:a, 1}, {:b, 2}])
# 68
Map.new(["a", "bb"], fn s -> {s, String.length(s)} end)
# 69
Map.new([1, 2], fn x -> {x, x} end) |> Map.get(1)
# 70
Map.new([{:a, 1}, {:a, 2}])[:a]
Gabarito:
%{a: 1, b: 2}%{"a" => 1, "bb" => 2}12
16. Map.get/2 e Map.get/3
Conceito
Busca um valor pela chave. Sem o padrão, retorna nil. Com o padrão, retorna o padrão se a chave não existir.
iex> Map.get(%{a: 1}, :a)
1
iex> Map.get(%{a: 1}, :z)
nil
iex> Map.get(%{a: 1}, :z, 0)
0
Equívocos comuns
-
"Map.get lança erro se a chave não existe." Não lança. Retorna
nil(ou o padrão). -
"Posso usar
map[:chave]." Pode, masmap[:chave]não aceita padrão e é menos idiomático.
Exercícios
# 71
Map.get(%{"gato" => [1, 2]}, "gato")
# 72
Map.get(%{"gato" => [1, 2]}, "cachorro")
# 73
Map.get(%{"gato" => [1, 2]}, "cachorro", [])
# 74
~w(gato cachorro) |> Enum.flat_map(fn t -> Map.get(%{"gato" => [1]}, t, []) end)
Gabarito:
[1, 2]nil[][1]
17. Map.update/4
Conceito
Atualiza uma chave com base no valor atual. Se a chave não existe, insere o valor padrão.
iex> Map.update(%{a: 1}, :a, 0, fn v -> v + 10 end)
%{a: 11}
iex> Map.update(%{a: 1}, :b, 100, fn v -> v + 10 end)
%{a: 1, b: 100}
Contra-exemplos
# ❌ Se a chave existe, o valor padrão é ignorado
iex> Map.update(%{a: 1}, :a, 999, fn v -> v + 1 end)
%{a: 2}
# ❌ Se a chave não existe, a função NÃO é chamada
iex> Map.update(%{}, :a, 100, fn _ -> raise "nunca roda" end)
%{a: 100}
Equívocos comuns
- "A função sempre roda." Só roda se a chave existe.
- "O padrão sobrescreve." Só é usado se a chave não existe.
-
"update e put são iguais."
putsempre sobrescreve.updateacumula.
Exercícios
# 75
Map.update(%{}, :a, 1, fn v -> v + 100 end)
# 76
Map.update(%{a: 1}, :a, 999, fn v -> v + 1 end)
# 77
Map.update(%{a: 1}, :b, 0, fn v -> v + 1 end)
# 78
Enum.reduce([1, 1, 2], %{}, fn x, acc ->
Map.update(acc, x, 1, fn v -> v + 1 end)
end)
Gabarito:
%{a: 1}%{a: 2}%{a: 1, b: 0}%{1 => 2, 2 => 1}
18. Funções anônimas e capture &
Conceito
Funções são valores. & cria funções anônimas curtas. &1, &2, etc. referem-se aos argumentos.
iex> Enum.map([1, 2, 3], &(&1 * 2))
[2, 4, 6]
iex> Enum.map(["a", "b"], &String.upcase/1)
["A", "B"]
Contra-exemplos
# ❌ &1 fora de contexto de capture
iex> &1
** (CompileError) ...
# ❌ Capturar função de módulo sem /aridade
iex> &String.upcase
** (CompileError) ...
# ✅ Com /aridade
iex> &String.upcase/1
Equívocos comuns
-
"
&1funciona em qualquer lugar." Só dentro de&(...)ou como referência direta a função. - "Capture é mais rápido." Não. É só mais curto.
-
"Preciso decorar." Não precisa. Se ficar confuso, use
fn ... end.
Exercícios
Reescreva usando capture:
# 79 → capture
Enum.map([1, 2], fn x -> x + 1 end)
# 80 → capture
Enum.filter([1, 2, 3], fn x -> x > 1 end)
# 81 → capture
Enum.map(["a"], fn s -> String.upcase(s) end)
# 82 → capture
Enum.reject(~w(a o gato), fn t -> t in ~w(a o) end)
Gabarito:
Enum.map([1, 2], &(&1 + 1))Enum.filter([1, 2, 3], &(&1 > 1))Enum.map(["a"], &String.upcase/1)Enum.reject(~w(a o gato), &(&1 in ~w(a o)))
19. Pattern matching
Conceito
= é casamento de padrão. O lado esquerdo descreve a forma; o direito precisa casar.
iex> {a, b} = {1, 2}
iex> a
1
iex> [h | t] = [1, 2, 3]
iex> h
1
iex> t
[2, 3]
iex> %{nome: n} = %{nome: "Ana", idade: 30}
iex> n
"Ana"
Contra-exemplos
# ❌ Tamanhos diferentes não casam
iex> {a, b} = {1, 2, 3}
** (MatchError) ...
# ❌ Chave inexistente não casa
iex> %{x: n} = %{a: 1}
** (MatchError) ...
# ✅ Chave com padrão obrigatório
iex> %{idade: i} = %{nome: "Ana", idade: 30}
iex> i
30
Equívocos comuns
-
"
=é atribuição." É casamento. Ema = 1comalivre, funciona como atribuição — mas comajá ligado, é comparação. - "Pattern matching em mapas exige todas as chaves." Não. Só as mencionadas no padrão.
- "Não posso aninhar." Pode, e muito.
iex> %{dados: %{nome: n}} = %{dados: %{nome: "Ana", idade: 30}}
iex> n
"Ana"
Exercícios
# 83
{a, b} = {10, 20}
a + b
# 84
[h | _] = [1, 2, 3]
h
# 85
[_, x | _] = [1, 2, 3, 4]
x
# 86
%{nome: n} = %{nome: "Ana", idade: 30}
n
# 87
fn {t, id} -> "#{t}-#{id}" end.({"gato", 1})
# 88
fn {_, %{tf: tf}} -> tf end.({"gato", %{tf: 3, pos: [0]}})
Gabarito:
3012"Ana""gato-1"3
20. Guards
Conceito
when adiciona condições extras à cabeça de uma função. Só aceita funções "guard-safe".
defmodule M do
def dobro(x) when is_integer(x), do: x * 2
def dobro(x) when is_binary(x), do: String.duplicate(x, 2)
end
iex> M.dobro(5)
10
iex> M.dobro("ab")
"abab"
Contra-exemplos
# ❌ Função não guard-safe
defmodule M do
def valida(x) when String.length(x) > 0, do: x
end
** (CompileError) ...
# ✅ Use guard-safe
defmodule M do
def valida(x) when is_binary(x) and byte_size(x) > 0, do: x
end
Equívocos comuns
- "Qualquer função serve em guard." Não. Só guard-safe (checagens, comparações, aritmética simples).
-
"Guard é o mesmo que
if." Não. Guards são declarativas, ficam na cabeça da função.
Exercícios
# 89
defmodule Ex do
def tipo(x) when is_integer(x), do: :inteiro
def tipo(x) when is_binary(x), do: :string
def tipo(_), do: :outro
end
Ex.tipo(1)
Ex.tipo("a")
Ex.tipo(:a)
Gabarito: :inteiro, :string, :outro
21. Múltiplas cláusulas de função
Conceito
Uma mesma função pode ter várias cláusulas. Elixir tenta casar da primeira para a última.
defmodule F do
def calc(0), do: 1
def calc(n), do: n * calc(n - 1)
end
iex> F.calc(5)
120
Contra-exemplos
Ordem importa. Caso geral antes do específico mata o específico:
defmodule F do
def calc(n), do: n * calc(n - 1) # ❌ nunca chega no caso base
def calc(0), do: 1
end
Equívocos comuns
- "Ordem das cláusulas não importa." Importa, e muito.
- "Preciso cobrir todos os casos." Não precisa, mas se nenhum casar, dá erro em tempo de execução.
Exercícios
# 90
defmodule G do
def f([]), do: 0
def f([_ | t]), do: 1 + f(t)
end
G.f([1, 2, 3])
Gabarito: 3
22. Operador in
Conceito
Testa pertinência em coleção.
iex> "gato" in ["gato", "cachorro"]
true
iex> 5 in 1..10
true
iex> "x" in ~w(a b c)
false
Equívocos comuns
-
"
infunciona em qualquer coisa." Funciona em listas, ranges e outras coleções que implementamEnumerable. -
"
iné case-insensitive." Não é.
Exercícios
# 91
"de" in ~w(a o de)
# 92
"DE" in ~w(a o de)
# 93
5 in 1..10
# 94
11 in 1..10
# 95
~w(a o gato) |> Enum.reject(&(&1 in ~w(a o)))
Gabarito:
truefalsetruefalse["gato"]
23. :math.log/1
Conceito
Logaritmo natural, via Erlang.
iex> :math.log(1)
0.0
iex> :math.log(:math.exp(1))
1.0
iex> :math.log(10)
2.302585092994046
Equívocos comuns
-
"
Math.logdo JS é igual." Sim, mesmo log natural. -
"Preciso de
import." Não. Está disponível direto como:math.log/1. -
"Log de número negativo funciona." Retorna
nan.
Exercícios
# 96
:math.log(3) > :math.log(2)
# 97
:math.log(3) / :math.log(3)
# 98
:math.log(1) == 0.0
Gabarito: true, 1.0, true
24. Atributos de módulo (@)
Conceito
Constantes em tempo de compilação. @moduledoc e @doc documentam.
defmodule M do
@versao "1.0.0"
def versao, do: @versao
end
Equívocos comuns
- "Atributo é variável." Não. É avaliado em compilação e "congelado".
- "Posso mudar em runtime." Não.
Exercícios
# 99
defmodule C do
@stopwords ~w(a o de)
def eh_stop?(t), do: t in @stopwords
end
C.eh_stop?("de")
C.eh_stop?("gato")
Gabarito: true, false
25. def, defp, alias, use
Conceito
-
def— função pública. -
defp— função privada. -
alias— apelido de módulo. -
use— injeta código (usado em testes).
Equívocos comuns
- "defp é só uma convenção." Não. É de fato inacessível fora do módulo.
defmodule M do
def pub, do: priv()
defp priv, do: 42
end
iex> M.pub()
42
iex> M.priv()
** (UndefinedFunctionError) ...
Exercícios
# 100
defmodule N do
def a(x), do: b(x) + 1
defp b(x), do: x * 2
end
N.a(5)
Gabarito: 11
26. ExUnit e mix
Conceito
Elixir vem com ExUnit. mix é a ferramenta de build.
mix new projeto
cd projeto
mix test
iex -S mix
defmodule MeuTest do
use ExUnit.Case
test "soma" do
assert 1 + 1 == 2
end
end
Equívocos comuns
- "Preciso instalar ExUnit." Vem embutido.
-
"Preciso de um arquivo de config." O
mix.exsgerado já traz. -
"assert é igual a if."
assertfalha o teste se a condição for falsa.
Exercícios
Crie um projeto, escreva um teste simples e rode mix test. Não tem gabarito — só faça.
Treino final: mini-desafios
Sem consultar o gabarito. Escreva, teste, ajuste.
D1. Use pipe para transformar " O GATO SUBIU! " em ["gato", "subiu"] — ou seja: trim, downcase, remover pontuação, split e remover stopwords.
D2. Dada uma lista ~w(gato cachorro gato peixe gato), conte quantas vezes cada palavra aparece, retornando um mapa.
D3. Dada uma lista de {palavra, doc_id}, agrupe num mapa %{palavra => [doc_ids]} sem duplicatas, ordenado.
D4. Dado %{1 => "a b c", 2 => "a b"}, calcule o TF de "a" em cada documento, retornando %{doc_id => tf}.
D5. Escreva uma função intersecao/1 que recebe uma lista de listas e devolve os elementos presentes em todas, usando Enum.reduce/3.
Gabarito
# D1
" O GATO SUBIU! "
|> String.trim()
|> String.downcase()
|> String.replace(~r/[^\p{L}\p{N}\s]/u, " ")
|> String.split(~r/\s+/, trim: true)
|> Enum.reject(&(&1 in ~w(a o e de da do em no na um uma para com por que se)))
# => ["gato", "subiu"]
# D2
~w(gato cachorro gato peixe gato)
|> Enum.reduce(%{}, fn p, acc -> Map.update(acc, p, 1, &(&1 + 1)) end)
# => %{"gato" => 3, "cachorro" => 1, "peixe" => 1}
# D3
[{"gato", 1}, {"gato", 2}, {"peixe", 3}, {"gato", 1}]
|> Enum.uniq()
|> Enum.group_by(fn {p, _} -> p end, fn {_, id} -> id end)
|> Map.new(fn {p, ids} -> {p, Enum.sort(ids)} end)
# => %{"gato" => [1, 2], "peixe" => [3]}
# D4
%{1 => "a b c", 2 => "a b"}
|> Enum.flat_map(fn {id, txt} ->
txt
|> String.split()
|> Enum.count(&(&1 == "a"))
|> then(fn c -> {id, c} end)
end)
|> Map.new()
# => %{1 => 1, 2 => 1}
# D5
def intersecao([]), do: []
def intersecao([h | t]), do: Enum.reduce(t, h, &(&2 -- (&2 -- &1)))
intersecao([[1, 2, 3], [2, 3, 4], [2, 3, 5]])
# => [2, 3]
Conclusão
Se você digitou cada exemplo, viu cada erro de propósito e resolveu os mini-desafios, você está pronto.
O tutorial de índice invertido vai usar exatamente esses blocos:
-
|>,String.*,~w,~r/u Enum.map/flat_map/filter/reject/uniq/sort/sort_by/group_by/reduce/with_indexMap.new/get/update-
&, pattern matching, guards, múltiplas cláusulas -
in,:math.log/1,@atributos -
def,defp,alias, ExUnit, mix
Nos vemos lá. 🚀
Top comments (0)