Noções Básicas
Hello World
IO.puts exibe com uma nova linha
# IEx interactive shell
# run: iex
# simple values
iex> 1 + 2
3
iex> "hello" <> " world"
"hello world"
# atoms (constants where name is value)
iex> :ok
:ok
iex> :error
:error
# tuples
iex> {:ok, 42}
{:ok, 42}
# lists
iex> [1, 2, 3]
[1, 2, 3]Comentários
Apenas comentários de uma linha
# hello.exs
IO.puts("Hello, World!")
# run: elixir hello.exs
# or in iex: iex hello.exs
# string interpolation with #{}:
name = "Elixir"
IO.puts("Hello, #{name}!")
# Hello, Elixir!
# IO.inspect shows any value (useful for debugging):
IO.inspect([1, 2, 3], label: "nums")Shell Interativo iex
iex é o shell interativo do Elixir
# single line comment only
# Elixir has no multi-line comments
# script file (.exs) - executed directly
# elixir script.exs
# compiled module file (.ex) - compiled then run
# elixirc file.ex
# in IEx, reload a single module:
iex> r MyModule
# recompile the whole project:
iex> recompile()Dados Imutáveis
Os dados são imutáveis, as operações retornam novos valores
# in IEx:
iex> h Enum.map # docs for Enum.map/2
iex> h Enum # module docs
iex> i "hello" # type info about a value
iex> b GenServer # list behaviour callbacks
iex> s Enum.map # function spec
iex> t Enum.t # print type definitions
# exit IEx:
iex> System.halt(0)
# or press Ctrl+C twiceÁtomos
Átomos são constantes, começam com dois pontos
# Mix project layout:
my_app/
lib/ # source code (.ex)
my_app.ex
my_app/greeter.ex
test/ # ExUnit tests (.exs)
my_app_test.exs
mix.exs # project config & dependencies
config/ # config files
config.exs
# module names map to file paths:
# MyApp.Greeter -> lib/my_app/greeter.ex
defmodule MyApp.Greeter do
def hello(name), do: "Hi, #{name}"
endPattern Matching
Operador de Match
= é match, não atribuição
# atoms are constants where the name IS the value
:ok
:error
:success
:true # boolean true is an atom
:false # boolean false is an atom
nil # nil is also an atom
# create from a string:
String.to_atom("hello") # :hello
# atoms compared by name (alphabetical):
:apple < :banana # true
# existing? check:
Atom.to_string(:ok) # "ok"Ignorando Matches
_ ignora valores indesejados
# integers (arbitrary precision)
42
-7
0x1F # hex = 31
0o17 # octal = 15
0b1010 # binary = 10
# floats
3.14
-0.5
1.0e3 # 1000.0
# division always returns a float:
7 / 2 # 3.5
div(7, 2) # 3 (integer division)
rem(7, 3) # 1 (remainder)
# rounding:
round(3.6) # 4
trunc(3.9) # 3Pattern Matching em Funções
Múltiplas cláusulas fazem match em ordem
# double-quoted strings are UTF-8 binaries
"hello"
"Elixir" # UTF-8 supported
"#{1 + 1} cats" # "2 cats"
# single-quoted are charlists (list of codepoints)
'hello' == [104, 101, 108, 108, 111] # true
# heredoc for multiline strings:
"""
line 1
line 2
"""
is_binary("hi") # true
is_list('hi') # trueExpressão case
case realiza pattern matching
true
false
nil
# only false and nil are falsy:
if nil, do: "yes", else: "no" # "no"
if 0, do: "yes", else: "no" # "yes" (0 is truthy!)
if [], do: "yes", else: "no" # "yes" (empty list is truthy)
# strict boolean ops (require booleans):
true and false # false
true or false # true
not true # false
# truthy ops (any value, return the value):
nil && 1 # nil
1 && 2 # 2
nil || "x" # "x"Guardas
when adiciona condições de guarda
# list (linked list)
[1, 2, 3]
[head | tail] = [1, 2, 3] # head=1, tail=[2,3]
[:a, "b", 3] # heterogeneous ok
# tuple (fixed-size, contiguous memory)
{:ok, 42}
{1, 2, 3}
tuple_size({:a, :b, :c}) # 3
# keyword list (list of 2-tuples with atom keys)
[name: "Alice", age: 30]
# map (any keys)
%{"name" => "Bob", :age => 25}Dados Imutáveis
Listas Imutáveis
Todas as operações retornam novos dados
1 + 2 # 3
5 - 3 # 2
2 * 4 # 8
10 / 2 # 5.0 (always float)
div(7, 2) # 3 (integer division)
rem(7, 3) # 1 (remainder, sign of dividend)
abs(-5) # 5
round(3.6) # 4
trunc(3.9) # 3
# power (Elixir 1.12+):
Integer.pow(2, 10) # 1024
:math.pow(2, 10) # 1024.0 (float)Maps Imutáveis
Atualizações retornam um novo Map
1 == 1.0 # true (value equality, ignores type)
1 === 1.0 # false (strict, also checks type)
1 != 2 # true
1 !== 1.0 # true
2 > 1 # true
2 >= 2 # true
1 < 2 # true
1 <= 2 # true
# total ordering across types:
# number < atom < ref < fun < port < pid < tuple < map < list < bitstring
:atom > 1 # true
[1] > %{} # trueIgualdade por Referência
Compara valores, não referências
# strict (require boolean operands):
true and false # false
true or false # true
not true # false
# truthy (any operand, return the operand):
nil && 1 # nil
1 && 2 # 2
nil || "default" # "default"
false || 5 # 5
!nil # true
!1 # false
# short-circuit evaluation:
false and raise("never") # false (right side skipped)
true or raise("never") # true (right side skipped)String Concatenation
<> concatenates binaries (strings); ++ concatenates lists (including charlists). -- removes the first occurrence of each element on the right. Prefer interpolation over repeated <> for readability.
"hello" <> " " <> "world" # "hello world"
# interpolation (preferred):
name = "Alice"
"Hi, #{name}!" # "Hi, Alice!"
"Sum: #{1 + 2}" # "Sum: 3"
# charlist concatenation:
'abc' ++ 'de' # 'abcde'
# list concatenation and subtraction:
[1, 2] ++ [3, 4] # [1, 2, 3, 4]
[1, 2, 3] -- [2] # [1, 3] (removes first match)Match Operator
= matches the right side against the left pattern, binding variables. If values don't match, raises MatchError. Variables can be rebound (point to new values), but the underlying data is immutable.
# = is pattern matching, not assignment
x = 1
1 = x # ok (matches, since x is 1)
2 = x # raises MatchError
# destructuring:
{a, b} = {1, 2}
a # 1
b # 2
[left | rest] = [1, 2, 3] # left=1, rest=[2,3]
# variables can be rebound (data itself is immutable):
x = 1
x = 2 # rebinds x to 2Membership & Others
in/2 checks membership in lists, ranges, maps (keys), and binaries (substring). | prepends to a list and is also used in pattern matching for head/tail. |> is the pipe operator (covered in its own section).
# in/2 checks membership in an enumerable:
2 in [1, 2, 3] # true
:b in [:a, :b, :c] # true
"x" in "text" # true (substring in binary)
# range membership:
5 in 1..10 # true
# | prepends to a list:
[0 | [1, 2, 3]] # [0, 1, 2, 3]
# also used to split head/tail:
[head | tail] = [1, 2, 3]
# |> is the pipe operator (see Pipe section):
"hi" |> String.upcase() # "HI"Módulos
Definindo Módulos
defmodule define um módulo
# = matches right against left
x = 42
{a, b, c} = {1, 2, 3}
[a, b, c] = [:x, :y, :z]
# reusing a variable in one pattern forces equality:
{n, n} = {1, 1} # ok, n = 1
{n, n} = {1, 2} # MatchError (1 != 2)
# ignore values with _:
{_, b, _} = {1, 2, 3} # b = 2
# bind multiple at once:
{_, {x, y}} = {:ok, {3, 4}} # x=3, y=4Atributos de Módulo
@ define atributos de módulo
x = 1
# ^ pins a variable: match its current value, don't rebind
^x = 1 # ok (x already 1)
^x = 2 # MatchError
case {1, 2} do
{^x, second} -> "matched, second=#{second}"
_ -> "no match"
end
# "matched, second=2"
# in function heads:
def same_as_x?(^x), do: true
def same_as_x?(_), do: falseFunções Privadas
defp define funções privadas
{:ok, value} = {:ok, 42}
value # 42
{:error, reason} = {:error, :not_found}
reason # :not_found
# size must match:
{a, b} = {1, 2, 3} # MatchError (size 2 vs 3)
# nested matching:
{:user, {name, age}} = {:user, {"Alice", 30}}
name # "Alice"
# tagged tuples for results:
case File.read("missing.txt") do
{:ok, content} -> content
{:error, :enoent} -> "file not found"
endAninhamento de Módulos
Módulos podem ser aninhados
# head and tail:
[head | tail] = [1, 2, 3]
head # 1
tail # [2, 3]
# fixed first elements + rest:
[a, b | rest] = [1, 2, 3, 4]
a # 1
b # 2
rest # [3, 4]
# empty list matches only []:
[] = [] # ok
[] = [1] # MatchError
# classic recursion pattern:
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)Matching Maps
Map matching only checks the keys you name—a subset match succeeds even if extra keys exist. Use ^key => value to match a dynamic key. Structs match by name and fields.
# match on specific keys (subset is fine):
%{name: name} = %{name: "Alice", age: 30}
name # "Alice"
# match a specific value:
%{status: :active} = %{status: :active, id: 1} # ok
%{status: :closed} = %{status: :active} # MatchError
# variable as a key requires pinning:
key = :name
%{^key => value} = %{name: "Bob"}
value # "Bob"
# struct matching (also a map):
%User{name: n} = %User{name: "Carol", age: 40}Matching in Functions
Function clauses are tried in order; the first matching head wins. Pattern matching + guards replace if/else for dispatch. Include a catch-all clause (or FunctionClauseError fires on no match).
defmodule Geometry do
# clauses tried top to bottom; first match wins:
def area({:rectangle, w, h}), do: w * h
def area({:circle, r}), do: 3.14 * r * r
def area({:square, s}), do: s * s
def area(_), do: {:error, :unknown_shape}
end
Geometry.area({:rectangle, 3, 4}) # 12
Geometry.area({:circle, 2}) # 12.56
Geometry.area({:triangle, 3, 4}) # {:error, :unknown_shape}
# guards add conditions:
def classify(n) when n < 0, do: :negative
def classify(0), do: :zero
def classify(n) when n > 0, do: :positiveFunções
Funções Nomeadas
def define funções nomeadas
# data is never mutated; operations return new data
list = [1, 2, 3]
List.replace_at(list, 0, 99) # [99, 2, 3]
list # [1, 2, 3] (unchanged)
map = %{a: 1}
Map.put(map, :b, 2) # %{a: 1, b: 2}
map # %{a: 1} (unchanged)
# strings too:
s = "hello"
String.upcase(s) # "HELLO"
s # "hello"Argumentos Padrão
\\ define valores padrão
original = [1, 2, 3]
# prepend (fast, O(1)):
[0 | original] # [0, 1, 2, 3]
original # [1, 2, 3]
# append (slow, O(n)):
original ++ [4] # [1, 2, 3, 4]
original # [1, 2, 3]
# concatenation:
[1, 2] ++ [3, 4] # [1, 2, 3, 4]Funções Multi-cláusula
Faz match dos argumentos em ordem
m = %{name: "Alice", age: 30}
# Map.put (add or update):
Map.put(m, :age, 31) # %{name: "Alice", age: 31}
# update syntax (key MUST exist):
%{m | age: 31} # %{name: "Alice", age: 31}
# %{m | height: 170} # KeyError (height not present)
# add only if missing:
Map.put_new(m, :role, "admin") # adds :role
Map.put_new(m, :age, 99) # unchanged (age exists)
# delete:
Map.delete(m, :age) # %{name: "Alice"}Funções de Pipeline
|> operador pipe
# variables bind to values; rebinding is allowed but
# the bound VALUE never changes
x = [1, 2, 3]
y = x # y points to the same list
x = [4, 5] # x rebound, y still [1, 2, 3]
y # [1, 2, 3]
# there is NO in-place mutation API:
# list[0] = 99 -- not valid Elixir
# to "update", rebind the variable:
x = [99 | tl(x)] # x now points to a new listSharing & Efficiency
Persistent data structures share unmodified parts, so 'copies' are cheap (O(1) for list prepend, ~O(log n) for map updates). Immutability makes sharing safe across processes without locks or copying.
# immutability enables safe structural sharing:
base = [1, 2, 3, 4, 5]
shared = [0 | base] # [0, 1, 2, 3, 4, 5]
# `shared` reuses `base`'s nodes—no copy of the tail
# maps also share internally; updates are ~O(log n):
m = %{a: 1, b: 2, c: 3}
m2 = Map.put(m, :d, 4) # shares most of m
# because data is immutable, sharing is always safe—
# no defensive copies needed across processes.
pids = Enum.map(1..100, fn _ -> spawn(fn -> m end) end)Funções Anônimas
Funções Anônimas
fn..end define funções anônimas
[head | tail] = [1, 2, 3]
head # 1
tail # [2, 3]
hd([1, 2, 3]) # 1
tl([1, 2, 3]) # [2, 3]
# empty list has no head:
# hd([]) # raises ArgumentError
# prepend a first element:
[0 | [1, 2, 3]] # [0, 1, 2, 3]
# length is O(n):
length([1, 2, 3]) # 3Capturando Funções Nomeadas
&Module.function/arity captura funções
[1, 2] ++ [3, 4] # [1, 2, 3, 4]
[] ++ [1] # [1]
# subtraction removes first occurrence of each right element:
[1, 2, 2, 3, 2] -- [2] # [1, 2, 3, 2] (only first 2)
[1, 2, 3] -- [4] # [1, 2, 3] (no-op)
# flatten nested lists:
List.flatten([1, [2, [3, 4]], 5]) # [1, 2, 3, 4, 5]
# fold (left):
List.foldl([1, 2, 3], 0, fn x, acc -> x + acc end) # 6Closures
Funções anônimas capturam variáveis externas
# basic:
for n <- [1, 2, 3], do: n * 2 # [2, 4, 6]
# with filter:
for n <- 1..10, rem(n, 2) == 0, do: n # [2, 4, 6, 8, 10]
# multiple generators:
for x <- [:a, :b], y <- [1, 2], do: {x, y}
# [a: 1, a: 2, b: 1, b: 2]
# into: change the result container:
for n <- [1, 2, 3], into: %{}, do: {n, n * n}
# %{1 => 1, 2 => 4, 3 => 9}
# build a string:
for c <- ?a..?c, into: "", do: <<c>> # "abc"Funções Anônimas Multi-cláusula
Funções anônimas também suportam múltiplas cláusulas
List.first([1, 2, 3]) # 1
List.last([1, 2, 3]) # 3
List.delete([1, 2, 3], 2) # [1, 3]
List.delete_at([1, 2, 3], 1) # [1, 3]
List.insert_at([1, 2, 3], 1, 9) # [1, 9, 2, 3]
List.replace_at([1, 2, 3], 1, 9) # [1, 9, 3]
# wrap (ensure list):
List.wrap(nil) # []
List.wrap(1) # [1]
List.wrap([1, 2]) # [1, 2]
# duplicate:
List.duplicate(:x, 3) # [:x, :x, :x]Enumerating Lists
Enum works on any enumerable (lists, maps, ranges). map/filter/reduce are the workhorses. chunk_every/2 splits into groups of size n; zip/2 pairs elements from two lists.
Enum.map([1, 2, 3], fn x -> x * 10 end) # [10, 20, 30]
Enum.filter([1, 2, 3, 4], fn x -> x > 2 end) # [3, 4]
Enum.reduce([1, 2, 3], 0, &+/2) # 6
Enum.find([1, 2, 3], fn x -> x > 1 end) # 2
# membership:
1 in [1, 2, 3] # true
Enum.member?([1, 2, 3], 2) # true
# chunking:
Enum.chunk_every([1, 2, 3, 4], 2) # [[1, 2], [3, 4]]
# zip:
Enum.zip([:a, :b], [1, 2]) # [{:a, 1}, {:b, 2}]Flattening and Folding
List.flatten/1 removes nesting. Enum.reduce/3 is the idiomatic left fold. List.foldr/3 folds from the right. Folding with prepend builds a reversed list efficiently in O(n).
# flatten:
List.flatten([1, [2, 3], [4, [5]]]) # [1, 2, 3, 4, 5]
# flatten with a tail:
List.flatten([1, [2]], [3, 4]) # [1, 2, 3, 4]
# fold (Enum.reduce is a left fold):
Enum.reduce([1, 2, 3], 0, fn x, acc -> acc + x end) # 6
# right fold:
List.foldr([1, 2, 3], [], fn x, acc -> [x | acc] end) # [1, 2, 3]
# building a reversed list with reduce + prepend:
Enum.reduce([1, 2, 3], [], fn x, acc -> [x | acc] end) # [3, 2, 1]Módulo Enum
map e each
map retorna uma nova lista, each retorna :ok
{:ok, 42}
{1, 2, 3}
{}
{:point, 3, 4}
# from a list:
List.to_tuple([:a, :b, :c]) # {:a, :b, :c}
# duplicate elements:
Tuple.duplicate(:x, 3) # {:x, :x, :x}
# size (O(1)):
tuple_size({:a, :b, :c}) # 3filter e reject
filter mantém, reject exclui
t = {:ok, "hello", 42}
elem(t, 0) # :ok
elem(t, 1) # "hello"
elem(t, 2) # 42
# tuples are 0-indexed; out of range raises:
# elem(t, 5) # ArgumentError
# put_elem returns a NEW tuple:
put_elem(t, 1, "world") # {:ok, "world", 42}
t # unchanged: {:ok, "hello", 42}
# pattern matching is preferred over index access:
{:ok, msg, _} = t
msg # "hello"reduce
reduce agrega uma lista
# idiomatic return values:
def divide(_a, 0), do: {:error, :divide_by_zero}
def divide(a, b), do: {:ok, div(a, b)}
# handle with case:
case divide(10, 2) do
{:ok, result} -> "result: #{result}"
{:error, reason} -> "error: #{reason}"
end
# "result: 5"
# with chains matches across steps:
with {:ok, a} <- maybe_a(),
{:ok, b} <- maybe_b() do
a + b
endBusca
find retorna o elemento, find_value retorna o resultado da função
t = {1, 2, 3}
# put_elem returns a new tuple:
put_elem(t, 0, 99) # {99, 2, 3}
# tuples are immutable:
t # {1, 2, 3}
# append/prepend/insert create a NEW tuple (O(n)):
Tuple.append(t, 4) # {1, 2, 3, 4}
Tuple.insert_at(t, 1, 99) # {1, 99, 2, 3}
# delete:
Tuple.delete_at(t, 1) # {1, 3}Ordenação
sort_by ordena por uma chave especificada
t = {:a, :b, :c}
Tuple.append(t, :d) # {:a, :b, :c, :d}
Tuple.delete_at(t, 0) # {:b, :c}
Tuple.duplicate(:x, 3) # {:x, :x, :x}
Tuple.insert_at(t, 1, :z) # {:a, :z, :b, :c}
# conversions:
Tuple.to_list(t) # [:a, :b, :c]
List.to_tuple([1, 2, 3]) # {1, 2, 3}
# size:
tuple_size(t) # 3Módulo Stream
Sequências Preguiçosas
Stream é avaliado de forma preguiçosa
# literal syntax (list of 2-tuples with atom keys):
[name: "Alice", age: 30]
# equivalent to:
[{:name, "Alice"}, {:age, 30}]
# duplicate keys allowed:
[a: 1, a: 2] # [a: 1, a: 2]
# empty:
[]
# a keyword list is just a list:
is_list([a: 1]) # true
length([a: 1, b: 2]) # 2Streams Infinitos
Stream.iterate cria streams infinitos
kw = [name: "Alice", age: 30, role: :admin]
# access first value for a key:
kw[:name] # "Alice"
kw[:age] # 30
kw[:missing] # nil
# Keyword.get with a default:
Keyword.get(kw, :missing, "default") # "default"
# get ALL values for a key (when duplicates exist):
Keyword.get_values([a: 1, a: 2], :a) # [1, 2]
# strict fetch (raises if missing):
Keyword.fetch!(kw, :name) # "Alice"Streams Cíclicos
Stream.cycle faz loop infinitamente
kw = [a: 1, b: 2, c: 3]
Keyword.keys(kw) # [:a, :b, :c]
Keyword.values(kw) # [1, 2, 3]
Keyword.has_key?(kw, :a) # true
Keyword.put(kw, :d, 4) # [a: 1, b: 2, c: 3, d: 4]
Keyword.delete(kw, :b) # [a: 1, c: 3]
# merge (right side wins on conflicts):
Keyword.merge([a: 1], [a: 99, b: 2]) # [a: 99, b: 2]
# take/drop:
Keyword.take(kw, [:a, :c]) # [a: 1, c: 3]Streams de Recursos
Stream.resource gerencia recursos
# Keyword list: ordered, atom keys only, duplicates, O(n)
[name: "Alice", age: 30]
# Map: unordered, any keys, no duplicates, ~O(log n)
%{name: "Alice", age: 30}
# use keyword lists for function options (idiomatic):
String.split("a,b,c", ",", trim: true)
# use keyword lists for small ordered config:
Application.get_env(:my_app, :key, [])
# use maps for:
# - key/value data with mixed-type or string keys
# - frequent lookupsCommon Use Cases
Keyword lists shine as the last argument of functions for options (e.g., trim: true). The do/end block syntax is actually a keyword list in disguise: if(true, do: x, else: y). Config files use them heavily.
# function options (most common):
def greet(name, opts \\ []) do
prefix = Keyword.get(opts, :prefix, "Hello")
"#{prefix}, #{name}!"
end
greet("Alice", prefix: "Hi") # "Hi, Alice!"
# config files:
config :my_app, MyRepo,
pool_size: 10,
timeout: 5000
# do/end block sugar is a keyword list:
if true, do: :yes, else: :no
# is shorthand for:
if(true, do: :yes, else: :no)Operações com Listas
Noções Básicas de Listas
Listas são estruturas de lista encadeada
# atom keys (shorthand):
%{name: "Alice", age: 30}
# any keys (=>):
%{"name" => "Bob", 1 => :one, :ok => true}
# mixed keys:
%{:atom => 1, "string" => 2}
# from a keyword list:
Enum.into([a: 1, b: 2], %{}) # %{a: 1, b: 2}
# empty:
%{}
# size (O(1)):
map_size(%{a: 1, b: 2}) # 2Concatenação
++ concatena, -- remove
m = %{name: "Alice", age: 30, role: :admin}
# bracket access (nil if missing):
m[:name] # "Alice"
m[:missing] # nil
# dot access (atom keys only, raises if missing):
m.name # "Alice"
# m.missing # KeyError
# strict fetch:
Map.fetch!(m, :age) # 30
Map.fetch(m, :missing) # :error
# with a default:
Map.get(m, :missing, "default") # "default"List Comprehensions
comprehensions for suportam filtragem e transformação
m = %{name: "Alice", age: 30}
# update existing key (raises if missing):
%{m | age: 31} # %{name: "Alice", age: 31}
# add or update:
Map.put(m, :age, 31) # %{name: "Alice", age: 31}
Map.put(m, :role, :admin) # adds :role
# add only if missing:
Map.put_new(m, :role, :admin)
# delete:
Map.delete(m, :age) # %{name: "Alice"}
# merge (right side wins on conflicts):
Map.merge(%{a: 1}, %{a: 99, b: 2}) # %{a: 99, b: 2}Módulo List
Módulo List fornece operações com listas
m = %{a: 1, b: 2, c: 3}
Map.keys(m) # [:a, :b, :c] (order not guaranteed)
Map.values(m) # [1, 2, 3]
Map.has_key?(m, :a) # true
Map.take(m, [:a, :c]) # %{a: 1, c: 3}
Map.drop(m, [:b]) # %{a: 1, c: 3}
# transform:
Map.new([{:a, 1}, {:b: 2}]) # %{a: 1, b: 2}
Map.to_list(m) # [a: 1, b: 2, c: 3]
# nested update:
users = %{alice: %{age: 30}}
put_in(users, [:alice, :age], 31) # %{alice: %{age: 31}}Pattern Matching Maps
Map patterns match on a subset of keys—extra keys are fine. To match a variable key, pin it with ^. This makes maps excellent for destructuring API responses and configuration.
# subset matching (extra keys ignored):
%{name: name} = %{name: "Alice", age: 30}
name # "Alice"
# match a specific value:
%{status: :active} = %{status: :active, id: 1} # ok
# variable key (must be pinned):
key = :name
%{^key => value} = %{name: "Bob"}
value # "Bob"
# in case clauses:
case response do
%{status_code: 200, body: body} -> body
%{status_code: code} -> {:error, code}
endStructs
Structs are tagged maps with a fixed set of keys and defaults, defined with defstruct. They carry their module name (__struct__) and enforce their keys at compile time—use them for domain entities.
defmodule User do
defstruct name: "anon", age: 0, role: :user
end
# create:
%User{name: "Alice", age: 30}
# %User{age: 30, name: "Alice", role: :user}
# access:
u = %User{name: "Bob"}
u.name # "Bob"
u.__struct__ # User
# update (existing fields only):
%{u | age: 25} # %User{age: 25, name: "Bob", role: :user}
# unknown key raises at compile time:
# %User{height: 180} # KeyError / compile error
# pattern match:
%User{name: n} = u
n # "Bob"Operações com Maps
Criando Maps
%{} cria um Map
Enum.map([1, 2, 3], fn x -> x * 2 end) # [2, 4, 6]
Enum.map([1, 2, 3], &(&1 * 2)) # [2, 4, 6] (capture)
# each returns :ok (side effects only):
Enum.each([1, 2, 3], fn x -> IO.puts(x) end)
# prints 1, 2, 3; returns :ok
# with index:
Enum.with_index([:a, :b, :c]) # [{:a, 0}, {:b, 1}, {:c, 2}]
# over a map (yields {key, value} pairs):
Enum.map(%{a: 1, b: 2}, fn {k, v} -> {k, v * 10} end)
# [a: 10, b: 20]Acesso e Atualização
Sintaxe . requer chaves átomo
Enum.filter([1, 2, 3, 4, 5], fn x -> rem(x, 2) == 0 end) # [2, 4]
Enum.reject([1, 2, 3, 4, 5], fn x -> rem(x, 2) == 0 end) # [1, 3, 5]
# truthy filter (drop nil/false):
Enum.filter([nil, 1, false, 2], & &1) # [1, 2]
# filter + map in one pass via flat_map:
Enum.flat_map([1, 2, 3], fn x ->
if x > 1, do: [x * 10], else: []
end)
# [20, 30]Módulo Map
Módulo Map fornece funções de operação
# sum:
Enum.reduce([1, 2, 3, 4], 0, fn x, acc -> acc + x end) # 10
# shorthand with a captured operator:
Enum.reduce([1, 2, 3, 4], 0, &+/2) # 10
# build a map:
Enum.reduce([{:a, 1}, {:b, 2}], %{}, fn {k, v}, acc ->
Map.put(acc, k, v * 2)
end)
# %{a: 2, b: 4}
# without an initial acc: uses the first element (raises on empty):
Enum.reduce([1, 2, 3], fn x, acc -> x + acc end) # 6Structs
defstruct define structs
Enum.find([1, 2, 3, 4], fn x -> x > 2 end) # 3 (first match)
Enum.find([1, 2, 3], fn x -> x > 5 end) # nil (no match)
# find_value returns the FUNCTION result, not the element:
Enum.find_value([1, 2, 3], fn x -> x > 2 && x * 10 end) # 30
# with a default:
Enum.find([1, 2, 3], :none, fn x -> x > 5 end) # :none
# find_index:
Enum.find_index([:a, :b, :c], &(&1 == :b)) # 1
# existence checks (short-circuit):
Enum.any?([1, 2, 3], &(&1 > 2)) # true
Enum.all?([1, 2, 3], &(&1 > 0)) # truesort and sort_by
sort/1 ascending; sort/2 with :desc or a comparator. sort_by/2 extracts a key per element and sorts—cleaner than a custom comparator for structured data. min/max raise on empty lists.
Enum.sort([3, 1, 2]) # [1, 2, 3]
Enum.sort([3, 1, 2], :desc) # [3, 2, 1]
# custom comparator:
Enum.sort(["aa", "b", "ccc"], fn a, b -> String.length(a) <= String.length(b) end)
# ["b", "aa", "ccc"]
# sort_by (extract a key per element, sort by it):
Enum.sort_by([%{n: 3}, %{n: 1}, %{n: 2}], & &1.n)
# [%{n: 1}, %{n: 2}, %{n: 3}]
# min / max / min_max:
Enum.min([3, 1, 2]) # 1
Enum.max([3, 1, 2]) # 3
Enum.min_max([3, 1, 2]) # {1, 3}group_by and count
group_by/2 buckets elements by a key function into a map. count/1 counts all, count/2 counts matches. chunk_every splits by size, chunk_by by a changing key. uniq/1 removes duplicates (preserves order).
# group_by buckets elements by a key function:
Enum.group_by([1, 2, 3, 4, 5, 6], fn x -> rem(x, 3) end)
# %{0 => [3, 6], 1 => [1, 4], 2 => [2, 5]}
# group structs by a field:
Enum.group_by([%{t: :a}, %{t: :b}, %{t: :a}], & &1.t)
# count:
Enum.count([1, 2, 3]) # 3
Enum.count([1, 2, 3, 4], fn x -> x > 2 end) # 2
# chunk (split into groups):
Enum.chunk_every([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
Enum.chunk_by([1, 1, 2, 2, 3], & &1) # [[1, 1], [2, 2], [3]]
# uniq:
Enum.uniq([1, 1, 2, 3, 3]) # [1, 2, 3]Funções de String
Operações com Strings
Módulo String opera sobre strings UTF-8
# Stream is lazy—operations compose, nothing runs until consumed:
stream = Stream.map([1, 2, 3], fn x -> x * 2 end)
# #Stream<[enum: 1..3, funs: [...]]> (no computation yet)
# consume to force evaluation:
Enum.to_list(stream) # [2, 4, 6]
Enum.take(stream, 2) # [2, 4]
# pipeline of lazy ops:
[1, 2, 3]
|> Stream.map(&(&1 * 2))
|> Stream.filter(&(&1 > 2))
|> Enum.to_list() # [4, 6]Split e Join
split e join
# Stream.iterate(start, next) - infinite:
Stream.iterate(1, &(&1 + 1))
|> Enum.take(5) # [1, 2, 3, 4, 5]
# Stream.repeatedly(fun) - infinite calls:
Stream.repeatedly(fn -> :rand.uniform(10) end)
|> Enum.take(3) # e.g. [4, 7, 2]
# Stream.unfold(state, fun) - stateful infinite:
Stream.unfold(0, fn n -> {n, n + 1} end)
|> Enum.take(4) # [0, 1, 2, 3]
# fibonacci:
Stream.unfold({0, 1}, fn {a, b} -> {a, {b, a + b}} end)
|> Enum.take(8) # [0, 1, 1, 2, 3, 5, 8, 13]Contains e Replace
Verificação contains e substituição
# Stream.cycle repeats an enumerable forever:
Stream.cycle([:a, :b, :c])
|> Enum.take(7) # [:a, :b, :c, :a, :b, :c, :a]
# alternate a pattern:
Stream.cycle([true, false])
|> Enum.take(5) # [true, false, true, false, true]
# zip a finite list with a repeating pattern:
Enum.zip([1, 2, 3, 4, 5], Stream.cycle([:x, :y]))
# [{1, :x}, {2, :y}, {3, :x}, {4, :y}, {5, :x}]Interpolação
#{} interpolação de strings
# Stream.resource(open, next, close) manages external resources:
stream = Stream.resource(
fn -> File.open!("data.txt") end, # open once
fn file ->
case IO.read(file, :line) do
:eof -> {:halt, file}
line -> {[line], file}
end
end,
fn file -> File.close(file) end # close always
)
# consume line by line - the file closes when done:
stream |> Enum.take(3)Binaries e Charlists
Aspas duplas são binaries, aspas simples são charlists
# Enum: eager, returns a new collection, runs immediately:
1..1_000_000 |> Enum.map(&(&1 * 2)) |> Enum.filter(&(&1 > 2))
# builds two large intermediate lists
# Stream: lazy, composes, runs once when consumed:
1..1_000_000
|> Stream.map(&(&1 * 2))
|> Stream.filter(&(&1 > 2))
|> Enum.take(5) # only computes until 5 are found
# rule of thumb:
# - small/finite data, need it now -> Enum
# - huge/infinite data, or early exit -> StreamOperador Pipe
Pipe Básico
|> passa o valor da esquerda como primeiro argumento para a função da direita
# define with fn ... end:
add = fn a, b -> a + b end
add.(1, 2) # 3
# shorthand capture:
square = &(&1 * &1)
square.(5) # 25
# capture a named function:
upcase = &String.upcase/1
upcase.("hi") # "HI"
# multi-clause anonymous functions:
greet = fn
:morning -> "Good morning"
:evening -> "Good evening"
_ -> "Hello"
end
greet.(:morning) # "Good morning"Pipe de Múltiplos Passos
Operações encadeadas são mais claras
defmodule Math do
# public:
def square(x), do: x * x
# private (only callable within the module):
defp secret, do: 42
# multi-clause with guards:
def sign(n) when n > 0, do: 1
def sign(n) when n < 0, do: -1
def sign(0), do: 0
end
Math.square(4) # 16
Math.sign(-3) # -1
# Math.secret() # UndefinedFunctionError (private)Com Tuples
Desestruture primeiro, depois faça pipe
# capture a named function by module/name/arity:
upcase = &String.upcase/1
upcase.("hi") # "HI"
# capture a local function:
defmodule M do
def double(x), do: x * 2
def quad(x), do: double(x) |> double()
end
# partial application with the capture shorthand:
rem_10 = &rem(&1, 10)
rem_10.(23) # 3
# operators are functions too:
add = &+/2
add.(3, 4) # 7Default Arguments
\\ sets a default value for an argument, evaluated each call. Defaults generate multiple clause heads internally, which can interact subtly with explicit multi-clause functions—define defaults only in a header clause.
defmodule Greet do
# \\ sets a default value:
def hello(name, greeting \\ "Hello", punctuation \\ "!") do
"#{greeting}, #{name}#{punctuation}"
end
end
Greet.hello("Alice") # "Hello, Alice!"
Greet.hello("Alice", "Hi") # "Hi, Alice!"
Greet.hello("Alice", "Hey", "?") # "Hey, Alice?"
# defaults interact with multi-clause functions:
# define defaults only in a header clause (no body),
# then write explicit clauses for each arity.Multi-clause Functions
A named function can have many clauses (each with its own pattern/guard); the first match wins. Order clauses from most to least specific. The last clause is usually a catch-all to avoid FunctionClauseError.
defmodule FizzBuzz do
def fb(n) when rem(n, 15) == 0, do: "FizzBuzz"
def fb(n) when rem(n, 3) == 0, do: "Fizz"
def fb(n) when rem(n, 5) == 0, do: "Buzz"
def fb(n), do: to_string(n)
end
Enum.map(1..5, &FizzBuzz.fb/1)
# ["1", "2", "Fizz", "4", "Buzz"]
# clause order matters - first match wins, so put
# specific clauses before the general catch-all.Closures
Anonymous functions close over variables in scope, capturing them by value at definition time. Rebinding the variable afterward doesn't affect the closure. To share mutable state, use a process (see the Processes section).
# anonymous functions capture variables from their defining scope:
x = 10
f = fn -> x end
f.() # 10
# captured by VALUE at definition time:
x = 99
f.() # still 10 (the old binding)
# closures cannot share mutable state (data is immutable);
# to share state, use a process:
defmodule Counter do
def start, do: spawn(fn -> loop(0) end)
defp loop(n) do
receive do
{:inc, pid} -> send(pid, {:count, n + 1}); loop(n + 1)
end
end
endFluxo de Controle
cond
cond verifica condições em ordem
# |> passes the left value as the FIRST argument to the right call:
"hello" |> String.upcase() # "HELLO"
# equivalent to: String.upcase("hello")
[1, 2, 3] |> Enum.map(&(&1 * 2)) # [2, 4, 6]
# equivalent to: Enum.map([1, 2, 3], &(&1 * 2))
5 |> Integer.to_string() # "5"
:ok |> inspect() # ":ok"case
case é baseado em pattern matching
"1,2,3,4,5"
|> String.split(",")
|> Enum.map(&String.to_integer/1)
|> Enum.filter(&(&1 > 2))
|> Enum.sum()
# 12 (3 + 4 + 5)
# without pipes (harder to read, nested inside-out):
Enum.sum(
Enum.filter(
Enum.map(String.split("1,2,3,4,5", ","), &String.to_integer/1),
&(&1 > 2)
)
)if e unless
unless é a negação de if
# pipe into functions returning tuples, then destructure:
{:ok, content} =
"config.json"
|> File.read!()
|> Jason.decode!()
# use case when a step might fail:
"file.txt"
|> File.read()
|> case do
{:ok, body} -> body
{:error, _} -> "default"
end
# tap/2 runs a side effect without breaking the pipe:
[1, 2, 3]
|> Enum.map(&(&1 * 2))
|> tap(&IO.inspect(&1, label: "doubled"))
|> Enum.sum()Expressão with
with encadeia pattern matches
# an anonymous function in a pipe must be called with a dot:
[1, 2, 3]
|> (fn list -> Enum.reverse(list) end).()
# [3, 2, 1]
# cleaner: use a named function or capture:
[1, 2, 3] |> Enum.reverse() # [3, 2, 1]
# then/2 (Elixir 1.12+) applies a function to the piped value:
5 |> then(fn x -> x * x end) # 25
# useful for inline construction:
def greet(name) do
name |> then(&"Hi, #{&1}!")
endBest Practices
Design your own functions data-first (the value as the first argument) so they pipe well. Keep pipelines to a readable length; extract named functions for reuse and tests. Use tap/2 for side effects, not arbitrary prints.
# DO: one operation per step, readable top-to-bottom:
users
|> Enum.filter(& &1.active)
|> Enum.map(& &1.name)
|> Enum.sort()
# DO: extract long pipelines into named functions for reuse/tests:
def active_names(users) do
users |> Enum.filter(& &1.active) |> Enum.map(& &1.name)
end
# AVOID: more than ~5 steps in one pipe - split or name intermediates.
# AVOID: mixing side effects into a transform pipeline - use tap/2.
# AVOID: piping into functions whose first arg isn't the data -
# restructure with a small wrapper.
# design your own functions data-first so they pipe well.Recursão
Recursão Básica
Recursão processa listas
defmodule Greeter do
@moduledoc "A simple greeting module."
def hello(name), do: "Hello, #{name}!"
def goodbye(name), do: "Bye, #{name}!"
end
Greeter.hello("Alice") # "Hello, Alice!"
# nested modules form a hierarchy:
defmodule MyApp.Net.HTTP do
def get(url), do: "GET #{url}"
end
MyApp.Net.HTTP.get("/users")Recursão com Acumulador
Versão otimizada para recursão em cauda
defmodule Account do
# public API:
def balance(user), do: user.balance
# private helper (not exported):
defp normalize(name), do: String.downcase(name)
# one-liner and do/end forms:
def create(name) do
%{name: normalize(name), balance: 0}
end
end
Account.balance(%{balance: 100}) # 100
# Account.normalize("ALICE") # undefined (private)Recursão em Árvore
Trata listas aninhadas
# bring a module's functions into local scope (no prefix):
defmodule Colors do
import String
def shout(s), do: upcase(s) <> "!" # calls String.upcase
end
# only/except to narrow what's imported:
import Enum, only: [map: 2, filter: 2]
import String, except: [split: 2]
# import only functions or only macros:
import SomeModule, only: :functions
import SomeMacroModule, only: :macrosalias
alias gives a module a shorter local name (default: last segment). Use :as for custom names, and the multi-alias form alias MyApp.{A, B} to group. Aliases are lexical—only visible in the module that declares them.
# alias creates a short name for a module:
defmodule MyModule do
alias MyApp.Very.Long.Path.HTTPClient
def fetch(url) do
HTTPClient.get(url) # instead of MyApp.Very.Long.Path.HTTPClient.get
end
end
# custom alias name with :as:
alias MyApp.Net.HTTPClient, as: HTTP
# multiple aliases at once:
alias MyApp.{Repo, Schema.User}require
require is needed to invoke a module's macros (which are expanded at compile time). Functions don't need require. Logger.info/1 is a macro, so modules using Logger must require Logger first.
# require lets you use a module's MACROS (not needed for functions):
defmodule M do
require Integer
def even?(n) do
if Integer.is_even(n), do: :yes, else: :no # is_even is a macro
end
end
# Logger is a macro module, so require it first:
require Logger
Logger.info("starting up")
# Kernel is auto-required; its macros (if, unless, def) are always usable.use
use Module invokes Module's __using__/1 macro at compile time, which can inject any code (callbacks, defaults, requires). It's a flexible extension point—frameworks like GenServer, Phoenix, and Ecto build on it.
# use invokes the module's __using__/1 macro - a hook for setup:
defmodule MyServer do
use GenServer # injects callbacks & defaults
end
# implementing __using__/1 in your own module:
defmodule Tracer do
defmacro __using__(_opts) do
quote do
def traced_call(x), do: IO.inspect(x)
end
end
end
defmodule Demo do
use Tracer
end
Demo.traced_call(42) # prints 42
# always check the docs to see what use injects.Otimização de Tail Call
Recursão em Cauda
Recursão em cauda não consome espaço de pilha
defmodule AppConfig do
@max_retries 5
@timeout_ms 5000
@env Mix.env() # evaluated at compile time
def retry_count, do: @max_retries
def timeout, do: @timeout_ms
def call do
if @env == :prod, do: :prod_call, else: :dev_call
end
endRecursão Não em Cauda
A multiplicação ocorre após a chamada recursiva, não é recursão em cauda
defmodule Greeter do
@moduledoc """
Greets people.
Use `hello/1` to say hello.
"""
@doc "Says hello to `name`."
@spec hello(String.t()) :: String.t()
def hello(name), do: "Hello, #{name}!"
@doc false # hide from docs
def helper, do: :ok
end
# in IEx: h Greeter, h Greeter.helloFatorial com Recursão em Cauda
Use um acumulador para recursão em cauda
defmodule Plugin do
@callbacks []
# accumulate by re-reading and prepending:
defmacro register(name) do
quote do
@callbacks [unquote(name) | @callbacks]
end
end
register(:init)
register(:start)
def all, do: @callbacks
end
Plugin.all() # [:start, :init]@behaviour
@behaviour Module declares that this module implements another module's @callback specs (an interface). @impl true marks a function as implementing a callback and lets the compiler check arity/return type. Missing callbacks produce compile-time warnings.
# a behaviour defines callbacks other modules should implement:
defmodule Parser do
@doc "Parses input into a data structure."
@callback parse(String.t()) :: {:ok, term()} | {:error, term()}
end
defmodule JSONParser do
@behaviour Parser
@impl true
def parse(str), do: Jason.decode(str)
end
# if a callback is missing, the compiler warns.@impl and @callback
@impl true (or @impl GenServer) marks a function as a callback implementation, enabling the compiler to verify it matches a known callback and to warn about typos. Use @callback to declare an interface; @impl to fulfil it.
defmodule MyGenServer do
use GenServer
# @impl marks callback implementations:
@impl true
def init(state), do: {:ok, state}
@impl true
def handle_call(:get, _from, state), do: {:reply, state, state}
# without @impl, you get a warning if the function
# doesn't override a known callback.
end
# defining your own callbacks:
defmodule Sorter do
@callback compare(a :: term(), b :: term()) :: boolean()
endProcessos
Criando Processos
spawn cria processos leves
defmodule MyList do
# base case + recursive case:
def sum([]), do: 0
def sum([head | tail]), do: head + sum(tail)
def len([]), do: 0
def len([_ | tail]), do: 1 + len(tail)
end
MyList.sum([1, 2, 3]) # 6
MyList.len([:a, :b]) # 2
# recursion replaces loops; pattern matching handles the base case.Enviando Mensagens
send/receive para comunicação entre processos
# tail-recursive: the recursive call is the LAST operation,
# so BEAM reuses the stack frame (no growth):
defmodule MyList do
def sum(list), do: sum(list, 0)
# accumulator carries the running total:
defp sum([], acc), do: acc
defp sum([head | tail], acc), do: sum(tail, acc + head)
end
MyList.sum([1, 2, 3, 4]) # 10
# contrast: head + sum(tail) is NOT tail-recursive
# (the + waits for the recursive result).Timeout do receive
after define um timeout
defmodule MyList do
# reverse using an accumulator (tail-recursive):
def reverse(list), do: reverse(list, [])
defp reverse([], acc), do: acc
defp reverse([h | t], acc), do: reverse(t, [h | acc])
# map with accumulator (built reversed, then reversed back):
def map(list, fun), do: map(list, fun, []) |> reverse()
defp map([], _fun, acc), do: acc
defp map([h | t], fun, acc), do: map(t, fun, [fun.(h) | acc])
end
MyList.reverse([1, 2, 3]) # [3, 2, 1]Vinculação de Processos
spawn_link vincula processos, saem juntos
defmodule Deep do
# count leaves of a nested list:
def count_leaves([]), do: 0
def count_leaves([head | tail]) when is_list(head) do
count_leaves(head) + count_leaves(tail)
end
def count_leaves([_head | tail]), do: 1 + count_leaves(tail)
# flatten any nesting:
def flatten([]), do: []
def flatten([h | t]) when is_list(h), do: flatten(h) ++ flatten(t)
def flatten([h | t]), do: [h | flatten(t)]
end
Deep.count_leaves([1, [2, [3]], 4]) # 4Estado do Processo
Verifica o estado do processo
defmodule MyList do
# recursive map:
def map([], _fun), do: []
def map([head | tail], fun), do: [fun.(head) | map(tail, fun)]
# recursive filter:
def filter([], _fun), do: []
def filter([h | t], fun) do
if fun.(h), do: [h | filter(t, fun)], else: filter(t, fun)
end
end
MyList.map([1, 2, 3], &(&1 * 2)) # [2, 4, 6]
MyList.filter([1, 2, 3, 4], &(rem(&1, 2) == 0)) # [2, 4]Recursive Reduce
reduce threads an accumulator through recursive calls—it's tail-recursive and handles large lists efficiently. This is essentially how Enum.reduce is implemented. The accumulator holds the running result.
defmodule MyList do
# left fold, recursive and tail-recursive:
def reduce([], acc, _fun), do: acc
def reduce([h | t], acc, fun), do: reduce(t, fun.(h, acc), fun)
end
MyList.reduce([1, 2, 3, 4], 0, &+/2) # 10
# the recursive call is the LAST operation,
# so this runs in constant stack space even for huge lists.
# This is essentially how Enum.reduce is implemented.Passagem de Mensagens
Send e Receive
Recepção de mensagens com pattern matching
# spawn/1 creates a lightweight BEAM process:
pid = spawn(fn -> IO.puts("hello from process") end)
# prints "hello from process"; the process is now dead
# spawn/3 with module/function/args:
pid = spawn(Enum, :map, [[1, 2, 3], &(&1 * 2)])
# process info:
Process.alive?(pid) # false (already finished)
self() # current process PIDReceive em Loop
Recursão para recebimento contínuo
# send/2 puts a message in a pid's mailbox:
pid = spawn(fn ->
receive do
{:hello, from} -> send(from, {:hi, self()})
{:bye, _} -> :ok
end
end)
send(pid, {:hello, self()})
# receive in the current process:
receive do
{:hi, pid} -> IO.puts("got hi from #{inspect(pid)}")
after
1000 -> :timeout
endReceive Seletivo
after 0 implementa verificação não bloqueante
# a process that keeps state via tail recursion:
defmodule Counter do
def start(n), do: spawn(fn -> loop(n) end)
defp loop(state) do
receive do
{:inc, by} -> loop(state + by)
{:get, from} ->
send(from, {:count, state})
loop(state)
:stop -> :ok
end
end
end
c = Counter.start(0)
send(c, {:inc, 5})
send(c, {:get, self()})
receive do {:count, n} -> n end # 5Links and Termination
spawn_link/1 links two processes: if one exits abnormally, the other does too (Elixir's 'let it crash' philosophy). Set Process.flag(:trap_exit, true) to convert exits into {:EXIT, pid, reason} messages instead of dying.
# spawn_link: linked processes die together (fault tolerance):
pid = spawn_link(fn ->
receive do
:boom -> raise "crash"
end
end)
send(pid, :boom)
# BOTH processes die (the linked one raises, this one exits too)
# trap exits to handle instead of dying:
Process.flag(:trap_exit, true)
spawn_link(fn -> exit(:kaboom) end)
receive do
{:EXIT, from, reason} -> IO.puts("#{inspect(from)} died: #{reason}")
endMonitors
Process.monitor/1 watches a process one-way: when it dies, you get {:DOWN, ref, :process, pid, reason} without dying yourself. Use monitors when you want to observe without coupling lifecycles. Demonitor with Process.demonitor/1.
# monitors are one-way and don't kill the watcher:
pid = spawn(fn -> exit(:boom) end)
ref = Process.monitor(pid)
receive do
{:DOWN, ^ref, :process, ^pid, reason} ->
IO.puts("monitored process died: #{reason}")
end
# prints "monitored process died: boom"
# unlike links, the current process keeps running.
# monitors are asymmetric and unidirectional.
Process.demonitor(ref) # stop watching earlyTimeouts
The after clause in receive sets a timeout in ms; after 0 does a non-blocking check (returns immediately if no match). This enables polling, flush loops, and timeouts. Timeouts are crucial to avoid hanging forever.
# receive with after for a timeout:
receive do
{:data, x} -> x
after
1000 -> :no_response # 1 second
end
# after 0 = non-blocking check:
receive do
msg -> {:got, msg}
after
0 -> :empty
end
# flush helper (drain the mailbox):
def flush do
receive do
_ -> flush()
after
0 -> :ok
end
endGenServer
Definindo GenServer
use GenServer traz o behaviour
defmodule Counter do
use GenServer
# callback: initial state
@impl true
def init(initial), do: {:ok, initial}
# synchronous (call) - returns a reply
@impl true
def handle_call(:get, _from, state), do: {:reply, state, state}
# asynchronous (cast) - no reply
@impl true
def handle_cast({:inc, n}, state), do: {:noreply, state + n}
endTratando Calls
handle_call trata requisições síncronas
# start_link links the server to the caller (named or unnamed):
{:ok, pid} = GenServer.start_link(Counter, 0, name: MyCounter)
# register via the module name:
{:ok, pid} = GenServer.start_link(Counter, 0, name: __MODULE__)
# starting under a Supervisor (typical in real apps):
children = [
{Counter, 0}
]
Supervisor.start_link(children, strategy: :one_for_one)Tratando Casts
handle_cast trata requisições assíncronas
@impl true
def handle_call(:get, _from, state) do
{:reply, state, state}
end
@impl true
def handle_call({:add, n}, _from, state) do
new_state = state + n
{:reply, new_state, new_state}
end
# reply tuples:
# {:reply, reply, new_state}
# {:reply, reply, new_state, timeout | :hibernate}
# {:noreply, new_state} # reply later with GenServer.reply/2
# {:stop, reason, reply, new_state}API Cliente
Envolver interface do cliente
@impl true
def handle_cast({:inc, n}, state) do
{:noreply, state + n}
end
@impl true
def handle_cast(:reset, _state) do
{:noreply, 0}
end
# cast returns immediately - the caller doesn't wait for a reply.
# useful for fire-and-forget updates, logging, side effects.
# return tuples:
# {:noreply, new_state}
# {:noreply, new_state, timeout | :hibernate}
# {:stop, reason, new_state}Client API
Convention: put the client API (functions calling GenServer.call/cast) in the same module as the server callbacks. Callers use Counter.get()/inc(), unaware of call/cast details. This hides the messaging behind a clean function interface.
defmodule Counter do
use GenServer
# client API (called by other code):
def start_link(initial) do
GenServer.start_link(__MODULE__, initial, name: __MODULE__)
end
def get, do: GenServer.call(__MODULE__, :get)
def inc(n \\ 1), do: GenServer.cast(__MODULE__, {:inc, n})
# server callbacks:
@impl true
def init(initial), do: {:ok, initial}
@impl true
def handle_call(:get, _from, state), do: {:reply, state, state}
@impl true
def handle_cast({:inc, n}, state), do: {:noreply, state + n}
endhandle_info
handle_info/2 handles 'raw' messages —plain sends, :DOWN monitor notifications, and Process.send_after/3 timeouts. call and cast have their own handlers; everything else lands here. Always implement it to avoid mailbox buildup.
defmodule Heartbeat do
use GenServer
@impl true
def init(_) do
send(self(), :tick)
{:ok, 0}
end
# handle non-call/cast messages (plain sends, monitors, timeouts):
@impl true
def handle_info(:tick, count) do
IO.puts("tick ##{count}")
Process.send_after(self(), :tick, 1000) # schedule next
{:noreply, count + 1}
end
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
IO.puts("#{inspect(pid)} died: #{reason}")
{:noreply, state}
end
endSupervisor
Definindo Supervisor
Supervisor gerencia processos filhos
# generate a new project:
$ mix new my_app
# creates:
# my_app/
# lib/my_app.ex
# lib/my_app/application.ex
# test/my_app_test.exs
# mix.exs
# .formatter.exs
# README.md
# with a supervision tree (OTP app):
$ mix new my_app --sup
# inside an umbrella project:
$ mix new apps/child_appEstratégias de Reinício
Três estratégias de reinício
defp deps do
[
{:phoenix, "~> 1.7"},
{:ecto_sql, "~> 3.10"},
{:jason, "~> 1.4"},
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false}
]
end
# install:
$ mix deps.get
# list the dependency tree:
$ mix deps.tree
# why is a dependency included?
$ mix deps.unlock --unusedChild Specs
Spec de filho completo
$ mix new app # scaffold a project
$ mix deps.get # fetch dependencies
$ mix deps.compile # compile dependencies
$ mix compile # compile the project
$ mix run # start the app and keep it running
$ mix run -e "MyApp.hello()" # run an expression
$ iex -S mix # IEx with the project loaded
$ mix test # run the test suite
$ mix format # format source files
$ mix phx.server # (Phoenix) start the web server
$ mix help # list all available tasksMix Tasks
Custom tasks live in Mix.Tasks.* modules and use Mix.Task. The module name (after Mix.Tasks.) becomes the command (dots become underscores). @shortdoc shows up in mix help. Mix.shell() provides user I/O.
# define a custom task module:
defmodule Mix.Tasks.MyApp.Hello do
use Mix.Task
@shortdoc "Says hello"
def run(_args) do
Mix.shell().info("Hello from my task!")
end
end
# run it:
$ mix my_app.hello
# Hello from my task!Environments
Mix has three environments: :dev (default), :test, :prod. Mix.env() returns the current one. config/{env}.exs overrides config/config.exs per environment. Set MIX_ENV at build time (e.g., MIX_ENV=prod mix phx.server).
# mix.exs uses Mix.env() to branch per environment:
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod, # permanent in prod
deps: deps()
]
end
# config/config.exs:
import Config
config :my_app, key: :default
# config/dev.exs, config/prod.exs, config/test.exs override per env.
# select env at build time:
# MIX_ENV=prod mix compile
# MIX_ENV=prod mix phx.serverAgents
Criando Agent
Agent encapsula estado
# test/my_app_test.exs
defmodule MyAppTest do
use ExUnit.Case, async: true
test "addition" do
assert 1 + 1 == 2
end
test "lists have a head" do
[head | _] = [1, 2, 3]
assert head == 1
end
test "raises on missing key" do
assert_raise KeyError, fn -> %{a: 1}.b end
end
end
# run: mix testLeitura e Atualização
update para atualizar, get para ler
defmodule UserTest do
use ExUnit.Case
describe "name validation" do
test "requires a name" do
assert User.new(%{}) |> valid?() == false
end
test "accepts a non-empty name" do
assert User.new(%{name: "A"}) |> valid?() == true
end
end
describe "age" do
test "must be positive" do
assert {:error, _} = User.new(%{age: -1})
end
end
endCache Simples
Usar Agent para implementar um cache simples
defmodule DbTest do
use ExUnit.Case, async: false
# runs once before each test; context is merged into the test:
setup do
conn = open_conn()
on_exit(fn -> close_conn(conn) end)
{:ok, conn: conn}
end
# runs once for the whole module:
setup_all do
{:ok, schema: create_schema()}
end
test "uses conn", %{conn: conn} do
assert query(conn, "SELECT 1") == 1
end
endAssertions
assert checks its argument is truthy (with great error output on failure). refute is the inverse. assert_in_delta compares floats within a tolerance. Pattern assertions (assert {:ok, v} = ...) both check and bind.
assert 1 + 1 == 2
refute 1 == 2 # opposite of assert
assert_in_delta 3.14, 3.1415, 0.01 # floats within tolerance
assert_raise ArgumentError, fn -> hd([]) end
# pattern assertions (both check and bind):
assert {:ok, val} = parse("1")
assert %User{name: "A"} = build_user()
# catch exits/throws:
assert catch_exit(exit(:boom)) == :boom
# enum/map assertions:
assert length([1, 2]) == 2
assert user.age > 18Async Tests
async: true runs the test module concurrently with others—safe when tests don't share mutable state. start_supervised/1 starts a process tied to the test's lifecycle and tears it down automatically. Default to async: true; disable only when needed.
defmodule CounterTest do
use ExUnit.Case, async: true # concurrent with other modules
# each test gets its own Counter process, so no shared state:
setup do
{:ok, pid} = start_supervised(Counter)
%{pid: pid}
end
test "increments", %{pid: pid} do
Counter.inc(pid)
assert Counter.get(pid) == 1
end
test "starts at zero", %{pid: pid} do
assert Counter.get(pid) == 0
end
endSnippets de Elixir relacionados
Copy-paste ready code for common tasks.
Correspondência de Padrões
Correspondência de padrões é central no Elixir — usada em todo lugar.
Operador Pipe
Encadear chamadas de função com o operador pipe |>.
Processos e Mensagens
Spawnar processos leves e enviar mensagens.
GenServer
Construir processos de servidor com estado com o behaviour GenServer.
Supervisores e OTP
Construir árvores de supervisão tolerantes a falhas.
Protocolos e Enums
Polimorfismo via protocolos e o módulo Enum.
Operações de Enum e Stream
Operações funcionais de coleção em Elixir.
Metaprogramação com Macros
Escrever código que escreve código em tempo de compilação.
Was this helpful?