Advertisement
  1. Code
  2. Elixir

Erlang e Elixir, Parte 2: Tipos de Dados

Scroll to top
Read Time: 10 min
This post is part of a series called Introduction to Erlang and Elixir.
Introduction to Erlang and Elixir
Erlang and Elixir, Part 3: Functions

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:

  1. Chaves devem ser atoms
  2. Chaves são ordenadas, especificadas pelo desenvolvedor
  3. 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:

  1. Mapas permitem qualquer valor como chave
  2. 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.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.