Elixir para desenvolvedores JavaScript: o que te surpreende na primeira semana
1. Paradigma funcional: a primeira grande sacudida
A primeira semana com Elixir é um choque térmico para qualquer desenvolvedor JavaScript. O paradigma funcional não é apenas uma recomendação — é a lei. A imutabilidade de dados é a regra número um.
# JavaScript — você pode fazer isso sem culpa
let x = 10
x = x + 1 // x agora é 11
# Elixir — isso simplesmente não compila
x = 10
x = x + 1 # Erro! (match error)
Em Elixir, = não é atribuição — é pattern matching. Você não "reatribui" uma variável; você cria um novo vínculo. Isso força você a pensar em transformações de dados, não em mutações.
A ausência de loops tradicionais é outro choque. Adeus for, while e forEach. Em Elixir, você usa Enum.map e Enum.reduce:
# JavaScript
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map(n => n * 2)
# Elixir
numbers = [1, 2, 3, 4, 5]
doubled = Enum.map(numbers, fn n -> n * 2 end)
Pattern matching substitui if/else e switch de forma muito mais elegante:
# JavaScript
function describe(value) {
if (value === null) return "nulo"
if (typeof value === "number") return "número"
return "outro"
}
# Elixir
def describe(nil), do: "nulo"
def describe(value) when is_number(value), do: "número"
def describe(_), do: "outro"
2. Sintaxe que engana: parece Ruby, mas não é
A sintaxe de Elixir lembra Ruby, mas as semelhanças são superficiais. Funções são definidas com def e defp (públicas e privadas):
defmodule MathUtils do
def soma(a, b), do: a + b # pública
defp helper(a), do: a * 2 # privada
end
O operador pipe |> é uma das primeiras coisas que encanta desenvolvedores JavaScript. Ele transforma o encadeamento de funções em algo legível:
# JavaScript — encadeamento aninhado
const result = JSON.stringify(processData(filterData(getData())))
# Elixir — pipe operator
result = get_data()
|> filter_data()
|> process_data()
|> Jason.encode!()
E não há return explícito. O último valor da função é automaticamente o retorno:
# JavaScript
function sum(a, b) {
return a + b
}
# Elixir
def sum(a, b) do
a + b # último valor, é o retorno
end
3. Concorrência leve: o que o Event Loop do JS nunca te contou
JavaScript tem um event loop single-threaded. Elixir tem processos leves — milhares deles rodando simultaneamente sem travamentos.
# JavaScript — Promise.all para concorrência limitada
const results = await Promise.all(tasks.map(task => task()))
# Elixir — spawn para criar processos independentes
task1 = spawn(fn -> heavy_computation1() end)
task2 = spawn(fn -> heavy_computation2() end)
result1 = receive do {:result, data} -> data end
result2 = receive do {:result, data} -> data end
Cada processo Elixir é isolado, com sua própria pilha e heap. Se um processo quebra, os outros continuam. O modelo de tolerância a falhas é "deixe quebrar" — você não tenta prevenir erros, você os trata com supervisores que reiniciam processos automaticamente.
4. Recursão como estrutura de controle natural
Sem loops, a recursão se torna sua principal ferramenta para iteração. Elixir otimiza chamadas de cauda (tail call optimization), então recursão não causa estouro de pilha:
# JavaScript — loop for
function factorial(n) {
let result = 1
for (let i = 2; i <= n; i++) result *= i
return result
}
# Elixir — recursão com acumulador (tail-call optimized)
def factorial(n), do: factorial(n, 1)
defp factorial(1, acc), do: acc
defp factorial(n, acc), do: factorial(n - 1, acc * n)
Percorrer listas sem for ou while:
# JavaScript
function sumList(numbers) {
let total = 0
for (let n of numbers) total += n
return total
}
# Elixir
def sum_list([]), do: 0
def sum_list([head | tail]), do: head + sum_list(tail)
5. O ecossistema OTP: o que o JavaScript não tem (e sente falta)
OTP (Open Telecom Platform) é o superpoder do Elixir. GenServer é o equivalente a um "serviço" stateful e concorrente:
defmodule Counter do
use GenServer
# API pública
def start_link(initial) do
GenServer.start_link(__MODULE__, initial, name: __MODULE__)
end
def increment do
GenServer.call(__MODULE__, :increment)
end
# Callbacks
def handle_call(:increment, _from, state) do
{:reply, state + 1, state + 1}
end
end
Supervisores formam árvores de supervisão que reiniciam processos automaticamente quando falham. Tasks e Agents oferecem paralelismo simples:
# Executar tarefas em paralelo
task1 = Task.async(fn -> expensive_operation1() end)
task2 = Task.async(fn -> expensive_operation2() end)
result1 = Task.await(task1)
result2 = Task.await(task2)
6. Tratamento de erros: try/rescue vs. try/catch
A filosofia "let it crash" é radicalmente diferente do tratamento defensivo do JavaScript. Em Elixir, você frequentemente usa pattern matching em tuplas:
# JavaScript — try/catch defensivo
try {
const result = riskyOperation()
handleSuccess(result)
} catch (error) {
handleError(error)
}
# Elixir — pattern matching em tuplas {:ok, result} / {:error, reason}
case risky_operation() do
{:ok, result} -> handle_success(result)
{:error, reason} -> handle_error(reason)
end
O with encadeia operações que podem falhar:
with {:ok, user} <- find_user(id),
{:ok, validated} <- validate_user(user),
{:ok, saved} <- save_user(validated) do
{:ok, saved}
else
{:error, reason} -> {:error, reason}
end
7. Ferramentas e fluxo de desenvolvimento
mix substitui npm/yarn com uma abordagem mais integrada:
# Criar novo projeto
mix new meu_projeto
# Compilar
mix compile
# Rodar testes
mix test
# Executar
mix run lib/meu_projeto.ex
IEx (Interactive Elixir) é muito mais que um REPL:
iex> "hello" |> String.upcase() |> String.reverse()
"OLLEH"
# Recompilar módulos sem sair do REPL
iex> recompile()
ExUnit é o framework de testes nativo, familiar para quem usou Jest ou Mocha:
defmodule MeuModuloTest do
use ExUnit.Case
test "soma deve funcionar" do
assert MeuModulo.soma(2, 3) == 5
end
test "deve falhar com valores negativos" do
assert_raise ArgumentError, fn ->
MeuModulo.soma(-1, 5)
end
end
end
A primeira semana com Elixir é um exercício de desconstrução mental. Você abandona mutação, loops, herança de classes e tratamento defensivo de erros. Em troca, ganha concorrência real, tolerância a falhas nativa e um modelo de dados imutável que simplifica drasticamente o raciocínio sobre o código. Não é fácil, mas é transformador.
Referências
- Documentação oficial do Elixir — Guia completo da linguagem, incluindo introdução para iniciantes e referência da biblioteca padrão.
- Elixir School — Tutorial gratuito e progressivo sobre Elixir, com exemplos práticos e comparações com outras linguagens.
- Joy of Elixir — Livro interativo que ensina Elixir do zero, ideal para quem vem de JavaScript ou Ruby.
- Elixir for JavaScript Developers (Thoughtbot) — Artigo técnico comparando conceitos entre as duas linguagens, com foco em concorrência e imutabilidade.
- Learn Elixir: The Ultimate Guide (freeCodeCamp) — Guia abrangente com exemplos de código e explicações sobre OTP, pattern matching e GenServer.
- Elixir vs JavaScript: A Developer's Perspective (Dev.to) — Comparação prática entre as duas linguagens no dia a dia do desenvolvimento web.