Portuguese (Português) translation by Erick Patrick (you can also view the original English article)
Elixir é bem rica nos tipos de dados disponíveis. Os tipos básicos como integer
, float
, boolean
e string
existem, assim como o atom/símbolo
, list
, tuple
e anonymous function
. Aprenderemos sobre eles nesse tutorial.
Antes de começarmos: Executaremos esses exemplos no modo interativo da Elixir. Digitemos iex
no terminal para tanto. Para maiores informações, eis a Parte 1. (Usuários do Windows, executem iex.bat --werl
).
Prefácio
Elixir foi feita com meta-programação em mente. Ela é toda construída com macros: instruções únicas que realizam determinadas tarefas. Nada muito complicado, mas eis alguns exemplos reais:
1 |
if worked? do |
2 |
IO.puts("You just used a macro") |
3 |
end |
4 |
|
5 |
if :name? do |
6 |
IO.puts(:name) |
7 |
end |
if
é uma macro para o condicional if padrão que todos conhecemos. Elixir compila o resultado da macro para nós internamente.
Meta-Programação?
Fundamental para Elixir é a manipulação de expressões referenciadas.
Meta-programação nada mais é que criar código a partir de código (programas são capazes de tratar seu próprio código como dado, basicamente... um grande avanço).
Um programa Elixir pode ser representado como sua própria estrutura de dados.
Por exemplo, o bloco básico da Elixir são representados por uma tuple
com três elementos (mais sobre tuples
logo). A função sum(1,2,3)
é definida assim:
1 |
{:sum, [], [1, 2, 3]} |
Podemos obter a representação de qualquer macro usando a macro quote
:
1 |
iex> quote do: sum(1, 2, 3) |
2 |
{:sum, [], [1, 2, 3]} |
Nota: Além da macro quote
, temos a unquote
—podemos ler mais sobre ela na documentação da Elixir.
Assim, vemos que o primeiro elemento é o nome da função :sum
, o segundo é uma lista de palavras-chave
(mais abaixo) com metadados (branco nesse caso) e o terceiro é a lista de argumentos.
Todas as macros são criadas usando essas estruturas de dados, logo, nosso código tem a habilidade natural de modificar e recompilar a si. Assim, nossas aplicações pode criar seu próprio código, verificar por erros e corrigi-los, ou mesmo escanear um plugin recém adicionado e modificá-lo em tempo real.
As implicações para inteligência artifical são obviamente enormes—mas, primeiro, precisamos aprender o básico desse guia e apreder bem os vários tipos de dados usados pela Elixir antes de aprofundarmo-nos.
Tipos Básicos
Agora que estamos no console interativo, vejamos os Boolean
.
1 |
iex> true |
2 |
true |
3 |
iex> true == false |
4 |
false |
O comportamento padrão de true
e false
é suportado. Para verificar o valor, usamos a função is_boolean
.
1 |
iex> is_boolean(true) |
2 |
true |
3 |
iex> is_boolean(1) |
4 |
false |
Cada tipo tem uma função equivalente, por exemplo, is_integer/1
, is_float/1
ou is_number/1
verificarão se o argumento é um inteiro, ponto flutuante ou qualquer um.
Elixir sempre se refere a funções dessa forma, com uma barra seguida de um número significando o número de argumentos que ela recebe. Assim, is_boolean/1
requer 1 argumento (is_boolean(1)
).
Nota: Se precisar acessar a ajuda a qualquer momento, basta digitar h
e temos acesso a informação sobre como usar o console interativo. Também encontraremos informações sobre qualquer operação ou função da Elixir, bastando digitar h is_integer/1
para ver a documentação de is_integer/1
.
Matematicamente, também podemos usar funções como essas:
1 |
iex> round(3.58) |
2 |
4 |
3 |
iex> trunc(3.58) |
4 |
3 |
Em Elixir, quando a divisão é realizada em um integer
, sempre é retornado um float
.
1 |
iex> 5 / 2 |
2 |
2.5 |
3 |
iex> 10 / 2 |
4 |
5.0 |
Se quisermos o resto da divisão ou realizar divisões com inteiros, usamos rem/2
e div/2
:
1 |
iex> div(10, 2) |
2 |
5 |
3 |
iex> div 10, 2 |
4 |
5 |
5 |
iex> rem 10, 3 |
6 |
1 |
Nota: parênteses não são obrigatórios para invocar funções.
Também podemos usar qualquer binary
, octal
ou hexadecimal
no iex
.
1 |
iex> 0b1010 |
2 |
10 |
3 |
iex> 0o777 |
4 |
511 |
5 |
iex> 0x1F |
6 |
31 |
Átomos (Simbolos)
São como constants
, mas seus nomes são seus próprios valores. Algumas linguagens os chamam de símboloso. Booleanos true
e false
são exemplos de símbolos.
1 |
iex> :hello |
2 |
:hello |
3 |
iex> :hello == :world |
4 |
false |
Tuplas
Similar a lists
e criadas com chaves, podemo conter qualquer tipo de dado:
1 |
iex> {:ok, "hello"} |
2 |
{:ok, "hello"} |
3 |
iex> tuple_size {:ok, "hello"} |
4 |
2 |
Listas
Similar às tuplas
, definimos uma list
com colchetes, assim:
1 |
iex> [1, 2, true, 3] |
2 |
[1, 2, true, 3] |
3 |
iex> length [1, 2, 3] |
4 |
3 |
Duas listas podem ser concatenadas e subtraídas:
1 |
iex> [1, 2, 3] ++ [4, 5, 6] |
2 |
[1, 2, 3, 4, 5, 6] |
3 |
iex> [1, true, 2, false, 3, true] -- [true, false] |
4 |
[1, 2, 3, true] |
Para pegar o começo ou fim de uma lista, usamos hd
e tl
('head' e 'tail').
Qual a Diferença Entre Listas e Tuplas?
Lists
são armazenadas na memória como listas encadeadas, uma lista de chave-valor que é iterada e acessada em operação linear. Atualização é rápida desde que anexemos à list
, se modificarmos o meio dela, será mais lento.
Tuples
, por outro lado, são armazenadas juntas na memória. Isso significa que obter o tamanho total dela ou acessar um dos elementos é rápido. Mas, em comparação à listas, anexar a uma tuple
existente é lento e requer copiar a tuple
toda para outro lugar, em memória.
Funções Anônimas
Definimos uma usando as palavras-chave fn
e end
.
1 |
iex> myFunc = fn a, b -> a + b end |
2 |
#Function<12.71889879/2 in :erl_eval.expr/5> |
3 |
iex> is_function(myFunc) |
4 |
true |
5 |
iex> is_function(myFunc, 2) |
6 |
true |
7 |
iex> is_function(myFunc, 1) |
8 |
false |
9 |
iex> myFunc.(1, 2) |
10 |
3 |
O manual da Elixir diz que Funções Anônimas são "cidadãos de primeira classe". Isso quer dizer que podemos usá-las como argumentos para outras funções assim como com inteiros.
Assim, no exemplo, declaramos uma função anônima na variável myFunc
para função de checagem is_function(myFunc)
, que retornar true
corretamente. Isso quer dizer que criamos nossa primeira função com sucesso.
Também podemos checar o número de argumentos de myFunc
invocando is_function(myFunc, 2)
.
Nota: usar um ponto (.
) entre a variável e o parênteses é obrigatório para invocar uma função anônima.
Cadeias de Caracteres
Definimos Cadeias de Caracteres em Elixir com aspas duplas:
1 |
iex> "yo" |
2 |
"yo" |
Interpolação se dá pelo uso do #
:
1 |
iex> "Mr #{:Smith}" |
2 |
"Mr Smith" |
Para concatenação, usamos <>
.
1 |
iex> "foo" <> "bar" |
2 |
"foobar" |
Para obter o tamanho de uma, usamos String.length
:
1 |
iex> String.length("yo") |
2 |
2 |
Para dividir usando um padrão, podemos usar String.split
:
1 |
iex> String.split("foo bar", " ") |
2 |
["foo", "bar"] |
3 |
|
4 |
iex> String.split("foo bar!", [" ", "!"]) |
5 |
["foo", "bar", ""] |
Listas de Palavras-Chave
Comumente usado em programação, um par de chave-valor de dois dados (duas tuples
), pode ser criado dessa forma, com um atom
como chave.
1 |
iex> list = [{:a, 1}, {:b, 2}] |
2 |
[a: 1, b: 2] |
3 |
iex> list == [a: 1, b: 2] |
4 |
true |
Em Elixir definimos listas assim [chave: valor]
. Dessa froma, quaisquer outro operador disponíveis em Elixir, como ++
, para anexar ao final ou começo da lista.
1 |
iex> list ++ [c: 3] |
2 |
[a: 1, b: 2, c: 3] |
3 |
iex> [a: 0] ++ list |
4 |
[a: 0, a: 1, b: 2] |
Há 3 pontos importantes sobre listas de palavras-chave:
- Chaves devem ser
atoms
- Chaves são ordenadas, especificadas pelo desenvolvedor
- Chaves podem ser atribuídas mais de uma vez
Para consultas a base de dados, a Biblioteca Ecto lança mão disso ao realizar consultas assim:
1 |
query = from w in Weather, |
2 |
where: w.prcp > 0, |
3 |
where: w.temp < 20, |
4 |
select: w |
O macro if
de Elixir também incorpora isso em seu design:
1 |
iex> if(false, [{:do, :this}, {:else, :that}]) |
2 |
:that |
No geral, quando uma lista é um argumento para uma função, os colchetes são opicionais.
Mapas
Similar a listas de palavras-chave, maps
existem para ajudar na necessidade de chave-valor. São criadas com %{}
:
1 |
iex> map = %{:a => 1, 2 => :b} |
2 |
%{2 => :b, :a => 1} |
3 |
iex> map[:a] |
4 |
1 |
5 |
iex> map[2] |
6 |
:b |
7 |
iex> map[:c] |
8 |
nil |
Elas são similares às listas de palavras-chave, mas não idênticas—há duas diferenças:
- Mapas permitem qualquer valor como chave
- Chaves dos mapas não seguem qualquer ordem
Nota: Ao criarmos todas as chaves em um map
como atoms
, a sintaxe de palavra-chave é bem conveniente.
1 |
iex> map = %{a: 1, b: 2} |
2 |
%{a: 1, b: 2} |
Comparação
Maps são ótimos para comparação de padrões, diferente de listas de palavras-chave. Quando map
é usado num padrão, sempre combinará a um subconjunto de um dado valor.
1 |
iex> %{} = %{:a => 1, 2 => :b} |
2 |
%{2 => :b, :a => 1} |
3 |
iex> %{:a => a} = %{:a => 1, 2 => :b} |
4 |
%{2 => :b, :a => 1} |
5 |
iex> a |
6 |
1 |
7 |
iex> %{:c => c} = %{:a => 1, 2 => :b} |
8 |
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1} |
Um padrão sempre casará desde que as chaves existam no mapa. Para casar com tudo, usamos um mapa vazio.
Para ir além, o Módulo Map provê uma API poderosa para manipulá-los:
1 |
iex> Map.get(%{:a => 1, 2 => :b}, :a) |
2 |
1 |
3 |
iex> Map.to_list(%{:a => 1, 2 => :b}) |
4 |
[{2, :b}, {:a, 1}] |
Estrutura de Dados Aninhadas
Dados geralmente requerem hierarquias e estruturas. Elixir provê suporte a maps
dentro de maps
ou listas de palavras-chave
dentro de maps
e por aí vai. Manipulação disso se dá pelo uso das macros put_in
e update_in
, que são usadas assim:
Digamos que temos o seguinte:
1 |
iex> users = [ |
2 |
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]}, |
3 |
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]} |
4 |
] |
5 |
[john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"}, |
6 |
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}] |
Então, agora temos alguns dados para manipular e cada lista de palavra-chave de usuários tem um mapa contendo o nome, idade e outras informações. Se quisermos acessar a idade de John, faremos assim:
1 |
iex> users[:john].age 27 |
Também usamos essa sintaxe para atualizar o valor:
1 |
iex> users = put_in users[:john].age, 31 |
2 |
[john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"}, |
3 |
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}] |
Expressões Regulares
Elixir usar o módulo :re
de Erlang, que é baseado na PCRE. Para mais informações, achamos na documentação.
Para criar uma expressão regular em Elixir, usamos Regex.compile
ou o atalho, ~r
e ~R
.
1 |
# A simple regular expression that matches foo anywhere in the string |
2 |
~r/foo/ |
3 |
|
4 |
# A regular expression with case insensitive and Unicode options |
5 |
~r/foo/iu |
O módulo Regex tem vários funções úteis que podemos usar para validar as expressões regulares, compilá-las e escapá-las corretamente. Eis alguns exemplos:
1 |
iex> Regex.compile("foo") |
2 |
{:ok, ~r"foo"} |
3 |
|
4 |
iex> Regex.compile("*foo") |
5 |
{:error, {'nothing to repeat', 0}} |
6 |
|
7 |
iex> Regex.escape(".") |
8 |
"\\." |
9 |
|
10 |
iex> Regex.escape("\\what if") |
11 |
"\\\\what\\ if" |
12 |
|
13 |
iex> Regex.replace(~r/d/, "abc", "d") |
14 |
"abc" |
15 |
|
16 |
iex> Regex.replace(~r/b/, "abc", "d") |
17 |
"adc" |
18 |
|
19 |
iex> Regex.replace(~r/b/, "abc", "[\\0]") |
20 |
"a[b]c" |
21 |
|
22 |
iex> Regex.match?(~r/foo/, "foo") |
23 |
true |
Também podem executar uma regex em um map
e obter o resultado com o método named_captures
:
1 |
iex> Regex.named_captures(~r/c(?<foo>d)/, "abcd") |
2 |
%{"foo" => "d"} |
3 |
|
4 |
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "abcd") |
5 |
%{"bar" => "d", "foo" => "b"} |
6 |
|
7 |
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, "efgh") |
8 |
nil |
Conclusão
Elixir é um ambiente completo de meta-programação baseado no uso fundamental de macros. Pode ajudar-nos, desenvolvedores, a projetar aplicações e estruturar dados em um novo nível, graças às poderosas lists
, maps
e anonymous functions
, quando usado com suas possibilidade de meta-programaçao.
Podemos ver soluções específicas em nossas abordagens, onde em linguagens que não usam macros levaria muitas linhas ou classes para criar, dada a riqueza poderosa oferecida pela Elixir na forma da DSL (Linguagem de Domínio Específico) e os módulos do Erlang.
Manipulação de pares chave-valor, list e manipulação básica de dados são só um pedaço de tudo o que é oferecido aos desenvolvedores. Cobriremos mais em mais detalhes nos próximos artigos da série.