DEV Community

Cover image for Introdução ao Elixir
mateusmartins
mateusmartins

Posted on

Introdução ao Elixir

Strings

Por padrão, uma string em Elixir sempre utilizará aspas duplas.
Por exemplo:

iex(1)> "mateus"
"mateus"
Enter fullscreen mode Exit fullscreen mode

Criando interpolação de strings:

iex(7)> x = "martins"
"martins"
iex(8)> "mateus #{x}"
"mateus martins"
Enter fullscreen mode Exit fullscreen mode

O que são esses /1, /2 que encontramos em algumas funções dentro das documentações?

São chamadas de Aridade da função.
E o que são?

Aridade nada mais é do que quantas argumentos a função recebe.
Por exemplo:

A função length/1 (do modulo String) só recebe um argumento.
E qual argumento é esse?

A string que eu quero saber o cumprimento:

iex(1)> String.length("mateus")
>6
Enter fullscreen mode Exit fullscreen mode

Neste caso, a função length está passando um único argumento, e me retornando o número de letras dentro dessa função.

Como obter as funções do módulo String:

String.
Enter fullscreen mode Exit fullscreen mode

Assim, obtemos todas as funções do módulo String.

E como sabemos o que cada função faz?

Para isso, podemos usar o h(helper) antes de passar as funções:

H String.slice

iex(2)> h String.slice


## Examples

    iex> String.slice("elixir", 1..3)
    "lix"

    iex> String.slice("elixir", 1..10)
    "lixir"

    iex> String.slice("elixir", -4..-1)
    "ixir"

    iex> String.slice("elixir", -4..6)
    "ixir"

For ranges where start > stop, you need to explicit mark them as increasing:

    iex> String.slice("elixir", 2..-1//1)
    "ixir"

    iex> String.slice("elixir", 1..-2//1)
    "lixi"

If values are out of bounds, it returns an empty string:

    iex> String.slice("elixir", 10..3)
    ""

    iex> String.slice("elixir", -10..-7)
    ""

    iex> String.slice("a", 0..1500)
    "a"

    iex> String.slice("a", 1..1500)
    ""


                        def slice(string, start, length)                        

  @spec slice(t(), integer(), non_neg_integer()) :: grapheme()



## Examples

    iex> String.slice("elixir", 1, 3)
    "lix"

    iex> String.slice("elixir", 1, 10)
    "lixir"

    iex> String.slice("elixir", 10, 3)
    ""

    iex> String.slice("elixir", -4, 4)
    "ixir"


Enter fullscreen mode Exit fullscreen mode

Agora na prática:

iex(4)> String.slice("MATEUSMARTINS", 2, 5)
"TEUSM"
Enter fullscreen mode Exit fullscreen mode

Outras formas de realiza-lar essa alteração:

iex(7)> x = "mateus"
"mateus"
iex(8)> String.downcase(x)
"mateus"
Enter fullscreen mode Exit fullscreen mode

Atoms

O que são atoms em Elixir?

São constantes onde o valor da constante é o próprio nome.
Exemplos:

iex(10)> :error
:error
iex(11)> :ok
:ok
iex(12)> :mateus
:mateus 
Enter fullscreen mode Exit fullscreen mode

Listas

É possível passar uma lista com vários “tipos” diferentes:

[1, 2, 3, 4.5, “string”]
Enter fullscreen mode Exit fullscreen mode

Exemplos de listas somadas:

iex(14)> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode
iex(15)> hd([1,2,3])
1
Enter fullscreen mode Exit fullscreen mode
iex(3)> tl([1,2,3])
[2, 3]
Enter fullscreen mode Exit fullscreen mode

Alguns outros exemplos:

iex(17)> x =  [1, 2, 3] ++ [4]  
[1, 2, 3, 4]

iex(18)> x
[1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Tuplas

Ao invés de se utilizar colchetes, utilizamos chaves:

iex(4)> {1,2,3}
{1, 2, 3}
Enter fullscreen mode Exit fullscreen mode

Qual a principal diferença entre Tuplas e Listas?

A tupla é armazenada quantiguamente na memória, ou seja, em endereços de memória um após o outro, seguintes.
Por exemplo:

iex(5)> x = {1, 2, 3, 4, 5, 6, 7}
{1, 2, 3, 4, 5, 6, 7}
iex(6)> elem(x, 4)
5
Enter fullscreen mode Exit fullscreen mode

Também podemos armazenar valores diferentes:

iex(7)> put_elem(x, 4, "banana")
{1, 2, 3, 4, "banana", 6, 7}
Enter fullscreen mode Exit fullscreen mode

Geralmente uma tupla tem 2 elementos apenas:

iex(9)> {:ok, "meu arquivo"}
{:ok, "meu arquivo"}
Enter fullscreen mode Exit fullscreen mode

Maps

São utilizados para quando precisamos armazenar chave e valor em uma estrutura de dados por acesso direto por chave.

Como criamos um mapa?

iex(1)> %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}
Enter fullscreen mode Exit fullscreen mode

Usando strings:

iex(3)> %{"a" => 1, "b" => 2, "c" => 3}
%{"a" => 1, "b" => 2, "c" => 3}
Enter fullscreen mode Exit fullscreen mode

Qual a diferença na hora de criarmos um map?

Atribuindo dentro de uma variável:

meu_map = %{a: 1, b: 2, c: 3}  
%{a: 1, b: 2, c: 3}

iex(5)> meu_map.a
1
Enter fullscreen mode Exit fullscreen mode

Porém, o mesmo não acontece com Strings.

Maps também podem ter valores mesclados.
Alguns meios de uso:

iex(18)> meu_map                   
%{a: 1, b: 2, c: 3}
iex(19)> Map.put(meu_map, "d", 5.5)
%{:a => 1, :b => 2, :c => 3, "d" => 5.5}
Enter fullscreen mode Exit fullscreen mode

Como podemos alterar um valor dentro de um map?
Neste exemplo criamos um map:

iex(6)> x = %{c: 5, d: 6, e: "sal", f: 5.5}
%{c: 5, d: 6, e: "sal", f: 5.5}

Enter fullscreen mode Exit fullscreen mode

Porém, gostaria de mudar o valor de “D”. Como podemos fazer isso?

Basta passar o map da seguinte maneira:

ex(7)> %{x | d: 10}                       
%{c: 5, d: 10, e: "sal", f: 5.5}
Enter fullscreen mode Exit fullscreen mode

Não é possível adicionar um valor, apenas alterar um já existente.
Um caso bem comum é na criação de usuários:

iex(8)> users = [%{name: "Mateus", age: 22}, %{name: "Lucas", age: 25}]
[%{age: 22, name: "Mateus"}, %{age: 25, name: "Lucas"}]
Enter fullscreen mode Exit fullscreen mode

Pattern Matching

Exemplos de Pattern Matching com mapas:

ex(1)> %{a: 1, b: 2, c: 3, d: 4}
%{a: 1, b: 2, c: 3, d: 4}
iex(2)> %{c: valor} = %{a: 1, b: 2, c: 3, d: 4}
%{a: 1, b: 2, c: 3, d: 4}
iex(3)> valor
3
Enter fullscreen mode Exit fullscreen mode

Exemplos com tuplas:

iex(4)> {:ok, result} =  {:ok, 14}
{:ok, 14}
iex(5)> result
14
Enter fullscreen mode Exit fullscreen mode

Criando uma função anonima: (sintaxe de uma função anonima: fn)

iex(1)> multiply = fn a, b -> a * b end
#Function<43.65746770/2 in :erl_eval.expr/5>
Enter fullscreen mode Exit fullscreen mode

Agora, com a função criada, executamos ela dessa maneira:

iex(2)> multiply.(2,3)
6
Enter fullscreen mode Exit fullscreen mode

Outro exemplo de função anonima:

iex(3)> read_file = fn
...(3)> {:ok, result} -> "Sucess #{result}" 
...(3)> {:error, reason} -> "Error #{reason}" 
...(3)> end
#Function<44.65746770/1 in :erl_eval.expr/5>
Enter fullscreen mode Exit fullscreen mode

Usando a função que foi criada:

iex(4)> read_file.(File.read("test.txt"))
"Error enoent"
Enter fullscreen mode Exit fullscreen mode

*Agora que aprendemos algumas funções básicas, vamos ao nosso primeiro projeto Elixir! *

Para criarmos um projeto em Elixir, basta realizar-mos o seguinte comando dentro do terminal:

mateusmartins@Mateuss-MacBook-Pro ~ % mix new name_project
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/name_project.ex
* creating test
* creating test/test_helper.exs
* creating test/name_project_test.exs

Enter fullscreen mode Exit fullscreen mode

Com o projeto criado, podemos abri-lo desta maneira no próprio terminal:

cd name_projct/
code .
Enter fullscreen mode Exit fullscreen mode

(No meu caso, usei palace_list)
Com isso, conseguiremos ver a estrutura básica de um projeto.

Algumas dicas do pacote mix:
Mix test = roda os testes da aplicação
Mix compile = compila a aplicação
Mix format = formata o código automaticamente

Pra rodar o iex dentro do seu projeto basta rodar o comando dentro do terminal:

iex -S mix
Enter fullscreen mode Exit fullscreen mode

Quando fazemos isso ele automaticamente compila o nosso código e carrega todo o nosso código no iex.

E como chamamos uma função específica de dentro de um módulo?

Para isso, chamamos o nome do módulo (no meu caso, PalaceList) e o nome da função (por exemplo: hello)
Ficando assim:

iex(2)> PalaceList.hello
:world
Enter fullscreen mode Exit fullscreen mode

Criando nossa primeira função dentro do projeto

Def (pra definir uma função), entre parênteses quais argumentos essa função recebe:

def sum([], acc) do
0
end

Depois criamos uma mesma função para definirmos o head, com o mesmo nome:

def sum([head | tail], acc) do
head
end

E quando rodamos o recompile e chamamos a função dentro do terminal, obtemos este resultado:

iex(7)> PalaceList.sum([1,2,3], 0)
1
Enter fullscreen mode Exit fullscreen mode

Podemos atribuir o retorno do Match a uma variável:

def sum([head | tail] =list, acc) do
list
end

iex(10)> PalaceList.sum([1,2,3], 0)
[1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

E agora estamos imprimindo a lista inteira.

iex(8)> PalaceList.sum([1,2,3], 0) 
6
iex(9)> PalaceList.sum([1,2,4], 0)
7
Enter fullscreen mode Exit fullscreen mode

Porém, ainda temos um bug que se eu chamar a lista com o “5” no final, ele retorna 5.

Para consertamos isso, ao invés de executarmos essa função direto, tornaremos essas funções PRIVADAS, ou seja, não permitiremos que o usuário execute elas, elas apenas são funções pra gente utilizar ela dentro do nosso módulo.

Para deixar-mos uma função privada alteramos o def para defp

E agora criaremos uma função chamada “call”, que irá chamar a função do módulo que é somar.

Criamos ela dessa maneira: def call(lista) (aqui ela recebe uma lista qualquer) , do: sum(List, 0) (e ela sempre irá chamar a nossa função privada SUM com a lista que recebemos e o acumulador 0)

Def call(lista), do: sum(list, 0)

Agora, quando executamos no terminal, quando chamamos uma lista vazia obtemos o “0”

iex(12)> PalaceList.call([])   
0

Enter fullscreen mode Exit fullscreen mode

E quando chamamos uma lista obtemos a soma:

iex(13)> PalaceList.call([1,2,3])
6

Enter fullscreen mode Exit fullscreen mode

Testando nossa aplicação

Dentro do nosso projeto, abriremos a pasta “test” e acessaremos o arquivo “palace_list_test.exs”

Dentro do nosso teste, podemos deletar o “doctest PalaceList”, pois ele executa todo o código que temos na documentação e ve se o código executa corretamente.
Como deletemos a documentação, não iremos utilizar o doctest.

Sempre teremos os testes com os nomes “exs”, enquanto os arquivos “comuns” serão “ex”.

Temos dentro do nosso arquivo de test um “use ExUnit.Case”. Mas o que é e para que serve?

Estamos trazendo todas as macros que temos pra usarmos no nosso test, trazendo todas as funcionalidades do ExUnit para dentro do módulo.

Todo test começaremos com “describe” acompanhado do nome da função que queremos testar junto da sua aridade.
Vale lembrar que sempre fazemos de funções PÚBLICAS.

No nosso caso, ficaria assim: describe “call/1” do
End

Usamos a “/“ para dizer qual é a aridade.
Poderíamos ter vários parâmetros, então dessa forma especificamos qual deles estamos testando.

Agora, iremos descrever o nosso test junto de uma descrição:
Test “return the list sum""

Agora, criaremos um setup para o nosso test:
List = [1, 2, 3]

E criaremos uma nova variável que irá armazenar a resposta
Chamaremos o nosso módulo “PalaceList” com a função “Call”
Não precisamos importar o módulo pois já temos acesso dentro do nosso test.

response = PalaceList.call(list)

Agora, criaremos uma resposta esperada:

expect_response = “mateus”

E, para executarmos o nosso test, utilizamos o assert.
O assert irá verificar se um valor é igual ao outro.

Faremos um primeiro test com valores diferentes para ele nos mostrar um erro.

No terminal, fora do iex, rodaremos o comando “mix test”, e ele irá nos retornar a seguinte mensagem de erro:

== Compilation error in file test/palace_list_test.exs ==
** (ExUnit.AssertionError) 

Assertion with == failed
code:  assert response == expect_response
left:  6
right: "mateus"
Enter fullscreen mode Exit fullscreen mode

Tínhamos o valor 6 de um lado, e recebemos “mateus”.

Agora, vamos voltar ao módulo de test e levar o resultado corretamente substituindo o nome pelo valor “6”.

Rodamos o test novamente e obtemos o resultado:

mix test

.

Finished in 0.01 seconds (0.00s async, 0.01s sync)
1 test, 0 failures
Enter fullscreen mode Exit fullscreen mode

Podemos realizar o teste de outra maneira também:

assert PalaceList.call([1, 2, 3]) == 6

Enum

Uma breve introdução ao módulo Enum

Normalmente, trabalhamos com o módulo Enum utilizando Listas, Maps ou Ranges.
Mudamos a nossa função para “def call_enum(list), do: Enum.sum(list)”, e dentro do terminal chamamos ela desta maneira:

iex(3)> PalaceList.call_enum([1, 2, 3, 4])
10
Enter fullscreen mode Exit fullscreen mode

Veja que o próprio Enum já possui uma função para somar todos os elementos.
Também podemos realizar um teste com valores mínimos ou máximos de uma lista, utilizando o próprio enum.
Desta forma, a função ficaria assim:

def call_enum(list), do: Enum.min(list)
Enter fullscreen mode Exit fullscreen mode

E o retorno de uma lista iex(5)> PalaceList.call_enum([1, 2, 3, 4]) seria “1”

Ou podemos transformar em valor máximo:

def call_enum(list), do: Enum.max(list)

E o maior valor da nossa lista seria o “4”.

E outra função muito utilizada é o Map

def call_enum(list), do: Enum.map(list, fn elem -> elem + 1 end)

Assim, criamos uma função dentro do map que irá cada valor dentro dessa lista:

iex(12)> PalaceList.call_enum([1, 2, 3, 4])
[2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

Por hoje é isso pessoal!
Nos vemos no próximo capítulo "Introdução ao Elixir - módulo 2""

Image description

Top comments (0)