DEV Community

Matheus de Camargo Marques
Matheus de Camargo Marques

Posted on

Fundamentos de Elixir: exercícios, contra-exemplos e equívocos

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:

  1. Conceito — explicação curta.
  2. Exemplos — o que funciona.
  3. Contra-exemplos — o que quebra e por quê.
  4. Equívocos comuns — o que quase todo mundo pensa errado.
  5. Exercícios — repita até virar automático.
  6. 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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "O pipe faz algo mágico." Não faz. É só reescrita. a |> f() é literalmente f(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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. 3
  2. Erro — String.upcase/1 espera string, mas String.length/1 devolve número.
  3. "CBA"
  4. "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"
Enter fullscreen mode Exit fullscreen mode

Contra-exemplos

# ❌ Não funciona com número
iex> String.downcase(42)
** (FunctionClauseError) ...

# ❌ Não funciona com nil
iex> String.downcase(nil)
** (FunctionClauseError) ...
Enter fullscreen mode Exit fullscreen mode

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("ÉÀÇ"))
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. "hello world"
  2. true
  3. true
  4. 3

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*"
Enter fullscreen mode Exit fullscreen mode

Contra-exemplos

# ❌ Substituindo por string vazia, sem pensar
iex> String.replace("fim.início", ".", "")
"fimíncio"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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", "")
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. "bonono"
  2. "abc"
  3. "a_b_c"
  4. "12345678900"
  5. "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"]
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "trim: true remove 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"]
Enter fullscreen mode Exit fullscreen mode

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", " ")
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. ["a", "b", "c"]
  2. ["a", "b", "", "c"]
  3. ["a", "b", "", "c"] — trim só tira das pontas
  4. ["a", "b", "c"]
  5. ["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"]
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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"]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. true
  2. false — ordem importa
  3. 4 — repetições contam
  4. 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
Enter fullscreen mode Exit fullscreen mode

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) ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 /u funciona 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
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. false — só letras
  2. true — exclamação
  3. false — só dígitos
  4. false — espaço é permitido por \s
  5. true — ponto
  6. false — vazio não casa com nada
  7. 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"]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. [2, 3, 4]
  2. ["aa", "bb"]
  3. [false, true, true]
  4. [1, 2]
  5. []

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. [1, 1, 2, 2, 3, 3]
  2. ["a", "b", "c", "d"]
  3. []
  4. [[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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "filter e reject são iguais com condição negada." São — mas escrever reject(&(&1 in stopwords)) é mais direto que filter(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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. [2, 3]
  2. [1]
  3. ["a", "o", "de"]
  4. ["gato"]
  5. [1, 2, 3]
  6. [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"]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. [1]
  2. [3, 1, 2]
  3. ["a", "A"] — Elixir diferencia maiúsculas e minúsculas
  4. [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"}]
Enter fullscreen mode Exit fullscreen mode

Contra-exemplos

sort/1 não consegue ordenar coisas incomparáveis:

iex> Enum.sort([1, "a", :ok])
** (ArgumentError) ...
Enter fullscreen mode Exit fullscreen mode

E sort/1 de strings é lexicográfico — maiúsculas antes de minúsculas:

iex> Enum.sort(["banana", "Abacaxi", "uva"])
["Abacaxi", "banana", "uva"]
Enter fullscreen mode Exit fullscreen mode

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 &1 só." Aceita qualquer função, capture incluído.
  • "Para inverter, uso reverse depois." Funciona, mas sort_by(fn x -> -x end) ou sort_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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. [1, 2, 3]
  2. ["a", "b", "c"]
  3. [3, 2, 1]
  4. ["uva", "banana", "abacaxi"]
  5. [{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]}
Enter fullscreen mode Exit fullscreen mode

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"]}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. %{0 => [3], 1 => [1, 4], 2 => [2, 5]}
  2. %{1 => ["a", "c"], 2 => ["bb", "dd"]}
  3. %{1 => ["A", "C"], 2 => ["BB"]}
  4. %{"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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "reduce é sempre o mais indicado." Não. map, filter, sum cobrem 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. reduce constró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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. 6
  2. 6
  3. "abc"
  4. [2, 4]
  5. %{"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}]
Enter fullscreen mode Exit fullscreen mode

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) ...
Enter fullscreen mode Exit fullscreen mode

A assinatura alternativa recebe uma função:

iex> Enum.with_index(["a", "b"], fn el, i -> {el, i + 100} end)
[{"a", 100}, {"b", 101}]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. [{"x", 0}, {"y", 1}]
  2. [{10, 0}, {20, 1}, {30, 2}]
  3. [{"a", 0}, {"b", 2}]
  4. [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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. %{a: 1, b: 2}
  2. %{"a" => 1, "bb" => 2}
  3. 1
  4. 2

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
Enter fullscreen mode Exit fullscreen mode

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, mas map[: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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. [1, 2]
  2. nil
  3. []
  4. [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}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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." put sempre sobrescreve. update acumula.

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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. %{a: 1}
  2. %{a: 2}
  3. %{a: 1, b: 0}
  4. %{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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "&1 funciona 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)
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. Enum.map([1, 2], &(&1 + 1))
  2. Enum.filter([1, 2, 3], &(&1 > 1))
  3. Enum.map(["a"], &String.upcase/1)
  4. 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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "= é atribuição." É casamento. Em a = 1 com a livre, funciona como atribuição — mas com a já 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"
Enter fullscreen mode Exit fullscreen mode

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]}})
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. 30
  2. 1
  3. 2
  4. "Ana"
  5. "gato-1"
  6. 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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "in funciona em qualquer coisa." Funciona em listas, ranges e outras coleções que implementam Enumerable.
  • "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)))
Enter fullscreen mode Exit fullscreen mode

Gabarito:

  1. true
  2. false
  3. true
  4. false
  5. ["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
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "Math.log do 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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) ...
Enter fullscreen mode Exit fullscreen mode

Exercícios

# 100
defmodule N do
  def a(x), do: b(x) + 1
  defp b(x), do: x * 2
end

N.a(5)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
defmodule MeuTest do
  use ExUnit.Case

  test "soma" do
    assert 1 + 1 == 2
  end
end
Enter fullscreen mode Exit fullscreen mode

Equívocos comuns

  • "Preciso instalar ExUnit." Vem embutido.
  • "Preciso de um arquivo de config." O mix.exs gerado já traz.
  • "assert é igual a if." assert falha 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]
Enter fullscreen mode Exit fullscreen mode

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_index
  • Map.new/get/update
  • &, pattern matching, guards, múltiplas cláusulas
  • in, :math.log/1, @ atributos
  • def, defp, alias, ExUnit, mix

Nos vemos lá. 🚀


Enter fullscreen mode Exit fullscreen mode

Top comments (0)