Baixar o código: learnelixir.ex
Elixir é uma linguagem funcional moderna construída no topo da Erlang VM. É totalmente compatível com Erlang, porém conta com uma sintaxe mais padronizada e muitos outros recursos.
# Comentários de linha única começam com um símbolo de número.
# Não há comentários de múltiplas linhas,
# mas você pode empilhar os comentários.
# Para usar o shell do Elixir use o comando `iex`.
# Compile seus módulos com o comando `elixirc`.
# Ambos devem estar em seu path se você instalou o Elixir corretamente.
## ---------------------------
## -- Tipos Básicos
## ---------------------------
# Há números
3 # integer
0x1F # integer
3.0 # float
# Atoms, que são literais, uma constante com nome. Elas começam com `:`.
:hello # atom
# Tuplas que são guardadas contiguamente em memória.
{1,2,3} # tupla
# Podemos acessar um elemento de uma tupla com a função `elem`:
elem({1, 2, 3}, 0) #=> 1
# Listas que são implementadas como listas ligadas.
[1,2,3] # lista
# Podemos acessar a primeira posição (head) e o resto (tail) de uma lista como a seguir:
[head | tail] = [1,2,3]
head #=> 1
tail #=> [2,3]
# Em Elixir, bem como em Erlang, o sinal `=` denota pattern match,
# e não uma atribuição.
#
# Isto significa que o que estiver à esquerda (pattern) é comparado com o que
# estiver à direita.
#
# É assim que o exemplo acima de acesso à head e tail de uma lista funciona.
# Um pattern match retornará erro quando os lados não conferem, como neste exemplo
# onde as tuplas tem diferentes tamanhos.
# {a, b, c} = {1, 2} #=> ** (MatchError) no match of right hand side value: {1,2}
# Também há binários
<<1,2,3>> # binary
# Strings e char lists
"hello" # string
'hello' # char list
# Strings de múltiplas linhas
"""
Strings
de múltiplas
linhas.
"""
#=> "Strings\nde múltiplas\nlinhas"
# Strings são sempre codificadas em UTF-8:
"héllò" #=> "héllò"
# Strings são de fato apenas binários, e char lists apenas listas.
<<?a, ?b, ?c>> #=> "abc"
[?a, ?b, ?c] #=> 'abc'
# `?a` em Elixir retorna o valor ASCII para a letra `a`
?a #=> 97
# Para concatenar listas use `++`, para binários use `<>`
[1,2,3] ++ [4,5] #=> [1,2,3,4,5]
'hello ' ++ 'world' #=> 'hello world'
<<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>>
"hello " <> "world" #=> "hello world"
# Ranges são representados como `início..fim` (ambos inclusivos)
1..10 #=> 1..10
menor..maior = 1..10 # Pattern matching pode ser usada em ranges também
[menor, maior] #=> [1, 10]
## ---------------------------
## -- Operadores
## ---------------------------
# Matemática básica
1 + 1 #=> 2
10 - 5 #=> 5
5 * 2 #=> 10
10 / 2 #=> 5.0
# Em Elixir o operador `/` sempre retorna um float.
# Para divisão de inteiros use `div`
div(10, 2) #=> 5
# Para obter o resto da divisão use `rem`
rem(10, 3) #=> 1
# Há também operadores booleanos: `or`, `and` e `not`.
# Estes operadores esperam um booleano como primeiro argumento.
true and true #=> true
false or true #=> true
# 1 and true #=> ** (ArgumentError) argument error
# Elixir também fornece `||`, `&&` e `!` que aceitam argumentos de qualquer tipo.
# Todos os valores exceto `false` e `nil` serão avaliados como true.
1 || true #=> 1
false && 1 #=> false
nil && 20 #=> nil
!true #=> false
# Para comparações temos: `==`, `!=`, `===`, `!==`, `<=`, `>=`, `<` e `>`
1 == 1 #=> true
1 != 1 #=> false
1 < 2 #=> true
# `===` e `!==` são mais estritos ao comparar integers e floats:
1 == 1.0 #=> true
1 === 1.0 #=> false
# Podemos comparar também dois tipos de dados diferentes:
1 < :hello #=> true
# A regra de ordenação no geral é definida abaixo:
# number < atom < reference < functions < port < pid < tuple < list < bit string
# Ao citar Joe Armstrong nisto: "A ordem de fato não é importante,
# mas que uma ordem total esteja bem definida é importante."
## ---------------------------
## -- Fluxo de Controle
## ---------------------------
# expressão `if`
if false do
"Isso nunca será visto"
else
"Isso será"
end
# Também há `unless`
unless true do
"Isso nunca será visto"
else
"Isso será"
end
# Lembra do pattern matching? Muitas estruturas de fluxo de controle em Elixir contam com ela.
# `case` nos permite comparar um valor com muitos patterns:
case {:um, :dois} do
{:quatro, :cinco} ->
"Isso não corresponde"
{:um, x} ->
"Isso corresponde e vincula `x` a `:dois`"
_ ->
"Isso corresponde a qualquer valor"
end
# É comum vincular o valor a `_` se não precisamos dele.
# Por exemplo, se apenas a head de uma lista nos interessa:
[head | _] = [1,2,3]
head #=> 1
# Para melhor legibilidade podemos fazer o seguinte:
[head | _tail] = [:a, :b, :c]
head #=> :a
# `cond` nos permite verificar várias condições ao mesmo tempo.
# Use `cond` em vez de aninhar vários `if`'s.
cond do
1 + 1 == 3 ->
"Nunca serei visto"
2 * 5 == 12 ->
"Nem eu"
1 + 2 == 3 ->
"Mas eu serei"
end
# É comum definir a última condição igual a `true`, que sempre irá corresponder.
cond do
1 + 1 == 3 ->
"Nunca serei visto"
2 * 5 == 12 ->
"Nem eu"
true ->
"Mas eu serei (isso é essencialmente um else)"
end
# `try/catch` é usado para capturar valores que são lançados, também suporta uma
# cláusula `after` que é invocada havendo um valor capturado ou não.
try do
throw(:hello)
catch
message -> "Deu #{mensagem}."
after
IO.puts("Sou o after.")
end
#=> Sou o after
# "Deu :hello"
## ---------------------------
## -- Módulos e Funções
## ---------------------------
# Funções Anônimas (repare o ponto)
square = fn(x) -> x * x end
square.(5) #=> 25
# Elas também aceitam várias cláusulas e guards.
# Guards permitem ajustes finos de pattern matching,
# sendo indicados pela palavra `when`:
f = fn
x, y when x > 0 -> x + y
x, y -> x * y
end
f.(1, 3) #=> 4
f.(-1, 3) #=> -3
# Elixir também fornece várias funções embutidas.
# Estas estão disponíveis no escopo atual.
is_number(10) #=> true
is_list("ola") #=> false
elem({1,2,3}, 0) #=> 1
# Você pode agrupar algumas funções em um módulo. Dentro de um módulo use `def`
# para definir suas funções.
defmodule Math do
def sum(a, b) do
a + b
end
def square(x) do
x * x
end
end
Math.sum(1, 2) #=> 3
Math.square(3) #=> 9
# Para compilar o módulo Math salve-o como `math.ex` e use `elixirc`
# em seu terminal: elixirc math.ex
# Dentro de um módulo podemos definir funções com `def` e funções privadas com `defp`.
# Uma função definida com `def` pode ser invocada por outros módulos,
# já uma função privada pode ser invocada apenas localmente.
defmodule PrivateMath do
def sum(a, b) do
do_sum(a, b)
end
defp do_sum(a, b) do
a + b
end
end
PrivateMath.sum(1, 2) #=> 3
# PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError)
# Declarações de funções também suportam guards cláusulas múltiplas:
defmodule Geometry do
def area({:rectangle, w, h}) do
w * h
end
def area({:circle, r}) when is_number(r) do
3.14 * r * r
end
end
Geometry.area({:rectangle, 2, 3}) #=> 6
Geometry.area({:circle, 3}) #=> 28.25999999999999801048
# Geometry.area({:circle, "not_a_number"})
#=> ** (FunctionClauseError) no function clause matching in Geometry.area/1
# Devido à imutabilidade, recursão é uma grande parte do Elixir
defmodule Recursion do
def sum_list([head | tail], acc) do
sum_list(tail, acc + head)
end
def sum_list([], acc) do
acc
end
end
Recursion.sum_list([1,2,3], 0) #=> 6
# Módulos do Elixir suportam atributos, hpa atributos embutidos e você
# pode também adicionar os seus próprios.
defmodule MyMod do
@moduledoc """
Este é um atributo embutido em um módulo de exemplo.
"""
@my_data 100 # Este é um atributo customizado.
IO.inspect(@my_data) #=> 100
end
## ---------------------------
## -- Structs e Exceptions
## ---------------------------
# Structs são extensões no topo de mapas que trazem valores padrão,
# garantias em tempo de compilação e polimorfismo para o Elixir.
defmodule Pessoa do
defstruct nome: nil, idade: 0, peso: 0
end
joe_info = %Pessoa{ nome: "Joe", idade: 30, peso: 180 }
#=> %Pessoa{idade: 30, peso: 180, nome: "Joe"}
# Acessa o valor de nome
joe_info.name #=> "Joe"
# Atualiza o valor de idade
older_joe_info = %{ joe_info | idade: 31 }
#=> %Pessoa{idade: 31, peso: 180, nome: "Joe"}
# O bloco `try` com a palavra `rescue` é usado para manipular exceções
try do
raise "algum erro"
rescue
RuntimeError -> "resgatado um erro em tempo de execução"
_error -> "isso resgatará qualquer erro"
end
# Toda exceção possui uma mensagem
try do
raise "algum erro"
rescue
x in [RuntimeError] ->
x.message
end
## ---------------------------
## -- Concorrência
## ---------------------------
# Elixir conta com o modelo de ator para concorrência. Tudo o que precisamos para
# escrever programas concorrentes em Elixir são três primitivos: spawning processes,
# sending messages e receiving messages.
# Para iniciar um novo processo usamos a função `spawn`, a qual leva uma função
# como argumento.
f = fn -> 2 * 2 end #=> #Function<erl_eval.20.80484245>
spawn(f) #=> #PID<0.40.0>
# `spawn` retorna um pid (process identifier), você pode usar esse pid para enviar
# mensagens ao processo. Para envio de mensagens usamos o operador `send`.
# Para tudo isso ser útil precisamos estar aptos a receber mensagens. Isto é
# realizado com o mecanismo `receive`:
defmodule Geometry do
def area_loop do
receive do
{:rectangle, w, h} ->
IO.puts("Area = #{w * h}")
area_loop()
{:circle, r} ->
IO.puts("Area = #{3.14 * r * r}")
area_loop()
end
end
end
# Compile o módulo e crie um processo que avalie `area_loop` no shell
pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0>
# Envia uma mensagem ao `pid` correspondente a um pattern na declaração de recebimento
send pid, {:rectangle, 2, 3}
#=> Area = 6
# {:rectangle,2,3}
send pid, {:circle, 2}
#=> Area = 12.56000000000000049738
# {:circle,2}
# O shell também é um processo, você pode usar `self` para obter o pid atual
self() #=> #PID<0.27.0>
Sugestões ou correções? Abra uma issue no repositório do GitHub, ou faça um pull request você mesmo!
Originalmente contribuído por Joao Marques e atualizado por 6 colaboradores.