Skip to content

Instantly share code, notes, and snippets.

@allanbatista
Last active April 22, 2022 12:54
Show Gist options
  • Save allanbatista/6c388878c83f3906d8ca4101786c07e3 to your computer and use it in GitHub Desktop.
Save allanbatista/6c388878c83f3906d8ca4101786c07e3 to your computer and use it in GitHub Desktop.
Elixir para programadores Python

Elixir para programadores Python

A motivação para a criação deste artigo começa com uma solicitação da minha equipe no B2WADS para fazer um tutorial de como usar o Elixir. Como todos aqui dominam Python, então decidi criar este tutorial específico para esses desenvolvedores.

O que é o Elixir

O Elixir é uma linguagem funcional e compilada que é executado em cima da ErlangVM. Foi projetada para ser de fácil entendimento e em muitos casos é muito similar a sintaxe do Ruby.

Podemos dizer que o Elixir é uma abstração para a linguagem Erlang, pois o Elixir é transpilado para Erlang antes de der convertido para BEAM bytecode que poisteriormente será executado na ErlangVM.

Open Telecom Platform (OTP)

"OTP is a set of libraries that ships with Erlang. Erlang developers use OTP to build robust, fault-tolerant applications. In this chapter we will explore how many aspects from OTP integrate with Elixir, including supervision trees, event managers and more;" (https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html)

Umbrella

O Projeto umbrella foi projetado para subdividir um projeto grande em projetos pequenos dentro de um único projeto grande.

TODO: https://elixirschool.com/pt/lessons/advanced/umbrella-projects/

Documentação da API do Elixir

Convensões e Stype Guide

Convensões no elixir é um ponto muito forte e precisamos ficar de olho nessa caracteristica para manter o código o mais legivel possível.

https://github.com/elixir-lang/elixir/blob/master/lib/elixir/pages/Naming%20Conventions.md
https://github.com/christopheradams/elixir_style_guide

Mix

Mix é o utilitário da linguagem para a execução de Tasks e podemos fazer muitas coisas com sua ajuda. Desde criar novos projetos à executar testes. Podemos encontrar a documentação do mix em https://hexdocs.pm/mix/Mix.Tasks.New.html

$ mix new demo_app --module DemoApp

O mix irá gerar uma estrutura básica para o projeto.

README.md
mix.exs # configuração das dependências e de start do projeto
config/config.exs # onde estará contido as informações de configurações do projeto
lib/demo_app.ex # namespace da aplicação
test/demo_app_test.exs # arquivo teste de exemplo
test/test_helper.exs # arquivo de teste helper

Terminal Interativo

Assim como python tem seu terminal interativo, o elixir também possui o seu, chamado de iex.

Uma vez instalado o elixir na máquina, é possível acessar o terminal interativo para poder executar simples operações de teste.

allan.batista@brrjobitanl0679:~$ iex
Erlang/OTP 21 [erts-10.2.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> IO.puts("Oi Programadores Python")
Oi Programadores Python
:ok

Acessando um projeto com IEX

Para carregar um projeto IEX, é necessário especificar um script de inicialização e neste caso, vamos passar o mix que já sabe fazer o trabalho pesado.

$ iex -S mix
iex> recompile # caso tenha alterado alguma coisa no projeto e queira recompilar sem precisar fechar e abrir novamente o iex

Tipos de dados

Já podemos começar dizendo que não existe uma convensão direta entre os tipos do elixir com os tipos do python, podemos salientar também que existem mais tipos de dados no python que no elixir. Atom.

Atoms

São constantes e quando comparadas, são sempre iguais. Vale notar que são case sensitivas. Atoms vão ser encontrados em todos os lugares no elixir. Desde parametros de funções à retornos.

iex(1)> :e
:e
iex(2)> is_atom(:e)
true
iex(3)> :e == :e
true
iex(4)> :e == :E
false
iex(5)> e = :E
:E
iex(6)> :e == e
false
iex(7)> :E == E
false
iex(8)> is_atom(E)
true

Porque :E é diferente de E.

Essa diferença ocorre por uma convensão do Elixir, que ao utilizar o Atom com letra maiuscula no inicio do nome sem os dois pontos (:) ele cria um atom com prefixo "Elixir." pois é o indentificador de um Module, falaremos de modules mais à frente.

Vamos utilizar o helper i para verificar os metadados dos atoms.

atom E

iex> i E
Term
  E
Data type
  Atom
Raw representation
  :"Elixir.E"
Reference modules
  Atom
Implemented protocols
  IEx.Info, Inspect, List.Chars, String.Chars

atom :E

  i :E
Term
  :E
Data type
  Atom
Reference modules
  Atom
Implemented protocols
  IEx.Info, Inspect, List.Chars, String.Chars

Provando

iex> :"Elixir.E" == E
true
iex> Elixir.E == E
true

Boolean

Boolean em elixir estar para o boolean do python Elixir possui os booleans convencionais, porém, nada mais são que Atoms.

iex(2)> true
true
iex(3)> false
false
iex(4)> true == true
true
iex(5)> false == false
true
iex(6)> :true == true
true
iex(7)> false == :false
true
iex(8)> true == false 
false
iex(9)> is_atom(false)
true
iex(10)> is_boolean(false)
true

Integer

Integer em elixir estar para o int do python.

Float

Float em elixir estar para o float do python.

String

String em elixir estar para o str do python.

Strings em Elixir são delimitados por aspas duplas (") e é codificado em UTF-8 por padrão. Strings são armazenas internamente como binário (binary), que são uma lista de bytes.

iex> "Minha String"
"Minha String"
iex> is_binary("Minha String")
true
iex> byte_size("Minha String")
12
iex> "Minha" <> " " <> "String" # concatena strings
"Minha String"
iex> "olá" <> " " <> <<77, 117, 110, 100, 111>> # concatena string com um binário
"olá Mundo"

Binary and Bitstrings

Existe uma outra notação especial para se trabalhar com binário (<<codepoint, codepoint>>)

Para trabalhar com strings usando sua notação binária.

iex> <<111, 108, 225>>
"olá"
iex> <<111, 108, 225>> <> " Mundo" # concatena um binário com uma string
<<111, 108, 225, 32, 77, 117, 110, 100, 111>>

TODO: Outros exemplos de binários

List

List em elixir estar para o list do python.

Elixir possui listas (Arrays) não tipadas, permitindo coleções de qualquer tipo de dados.

iex> is_list([1, 2, 3])
true
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]

Vale notar que listas em Elixir não podem ser acessadas por indice, isso por limitação da própria linguagem. Quando tentamos acessar uma lista por indice, é disparada uma exceção sugerindo a utilização de duas funções Access.fetch e Access.get que por incrivél que pareça não atende nosso propósito aqui por ele foi projetado para trabalhar com Map e Keyword Lists.

iex> [1,2,3][0]
** (ArgumentError) the Access calls for keywords expect the key to be an atom, got: 0
    (elixir) lib/access.ex:166: Access.fetch/2
    (elixir) lib/access.ex:179: Access.get/3

Com a utilização do Enum que faz parte da biblioteca nativa do Elixir. O retorno é um pouco diferente aqui, mas é bastante comum ter retorno nas funções dessa forma em Elixir. Uma Tupla com um symble e o resultado.

iex> Enum.fetch([1,2,3], 0)
{:ok, 1}
iex> Enum.fetch([1,2,3], 5)
:error

Tuple

Tuple em elixir estar para o tuple do python.

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2

List ou Tuple

Listas e Tuplas são muito parecidades em suas capacidades mas diferem principalmente porque listas são Linked (one um elemento aponta para o próximo) enquanto tuplas são armezanadas sequencialmente em memória.

Por experiência faz mais sentido usar tuplas para armazenas pequenas estruturas de dados que se saiba exatamente a posição de cada elemento como se fosse uma lista de parâmetros.

iex> {key, value} = {:name, "Allan"}
{:name, "Allan"}

Enquanto listas são usadas para armazenar dados de uma única representação.

iex> [1,2,3,4]

Keyword Lists

Keyword lists é uma convensão que deixa a sintexe mais bonita de se ter quando existe uma lista com tuplas, onde o primeiro elemento ta tupla é um atom.

Como são listas, podemos executar as mesmas operações que executamos em listas comuns.

iex> a = [{:name, "allan"}, {:lastname, "Batista}]
[name: "allan", lastname: "Batista"]
iex> b = [name: "Allan", lastname: "Batista"] # sintexe alternativa
[name: "Allan", lastname: "Batista"]
iex> a == b
true
iex> a[:name] # acessando um item pela sua key

Map

Map em elixir estar para o dict do python.

Maps são muito úteis para guardar informações de chave valor.

iex> a = %{:name => "Allan", "Last Name" => "batista"}
%{:name => "Allan", "Last Name" => "batista"}

Acessando Chaves

iex> a.name
"Allan"
iex> a[:name]
"Allan"
iex> a["Last Name"]
"batista"

Functions

Function em elixir estar para o methods/functions do python.

Funções com nomes só estão disponíves dentro de modules que funcionam como um namespace para as funções.

Podemos definir funções de duas formas:

def function_name(args) do
    IO.puts(args)
end

ou na versão comprimida

def function_name(args), do: IO.puts(args)

funções suportam overload

Quando uma função é chamada e não ocorre um match correto nos parâmetros, é disparado uma exceção.

defmodule C do
    def function_name([head | tail]), do: IO.puts(head)
    def function_name(%{name: name}), do: IO.puts(name)
    def function_name(_), do: "Not match"
end

Guards

Guards são funções auxiliares em elixir que auxiliam outras funções à validar execuções.

TODO

Funções anônimas

Função anônima em elixir esta para o lambda em python.

Esse tipo de função é bastante útil para execução de simples instruções ou delegação de instruções para outras funções.

iex> add = fn a, b -> a + b end     
#Function<12.128620087/2 in :erl_eval.expr/5>
iex> add.(2,3)
5

Note que a sintexe da chamada da função é um pouco distinta de outras línguagens, tendo um ponto (.) antes dos parênteses.

function_name.(param1, param2, param3...)

Operações

TODO

Operações com Listas

iex> a = [1,2,3] ++ [4,5,6] # concatena duas listas
[1, 2, 3, 4, 5, 6]
iex> a -- [2,3] # subtrai uma lista de outra
[1, 4, 5, 6]

Operações com Strings

iex> "Allan" <> " " <> "Batista"
"Allan Batista"

Operações com Booleans

iex> true and false # &&
false
iex> false or true # ||
true
iex> true or raise("Meu Raise")
true
iex> true and raise("Meu Raise")
** (RuntimeError) Meu Raise

Match

Em elixir não há o sinal de atribuição, isso mesmo. não existe. O sinal de igual (=) é utilizado para dar "match". É um pouco difícil até de explicar.

O match é composto por duas partes, o lado esquerdo diz o que deve ter no lado direito.

O match esta presente em todos os lugares, desde funções à atribuições.

Exemplo 1:

iex> %{name: name} = %{name: "Allan", age: 1}
%{name: "Allan", age: 1}
# o que conteceu aqui é que o match teve sucesso e não gerou nem um erro.
# Mas porque teve o mesmo resultado que o lado direito?
#   Isso é normal, mas nesse caso, a nossa intenção é pegar o valor da key :name e colocar em uma variável chamada "name"
iex> name
"Allan"

Exemplo 2:

iex> result = {:ok, "message ok"}
iex> case result do
    {:ok, message} -> IO.puts(message)
    {:error, message} -> IO.puts("Deu M: " <> message)
end

# com erro agora

iex> result = {:error, "message ok"}
iex> case result do
    {:ok, message} -> IO.puts(message)
    {:error, message} -> IO.puts("Deu M: " <> message)
end

Pin

O pin é utilizado quando não se deseja alterar o valor da variável do match.

iex> key = "abc"
iex> {^key, name} = {"abc", "Allan"}
{"abc", "Allan"} 
iex> {^key, name} = {"abc2", "Allan"}
** (MatchError) no match of right hand side value: {"abc2", "Allan"}

Aritimética

TODO

Modules

Module em elixir estar para class do python.

Modules são projetados para organizar o código em lacais que façam sentido estar juntos.

defmodule Pessoa do
    @moduledoc """
    Documentação do Módulo
    """
    
    @name "Allan"
    
    @doc """
    Documentação da função
    
    ## Examples
    
        iex> Pessoa.say_my_name()
        "Allan"
    """
    def say_my_name(), do: @name
    def say_my_name(nil), do: "Nil!"
    def say_my_name(name) when is_binary(name), do: name
    def say_my_name(%{name: name}), do: name
    def say_my_name(_), do: "Sei não"
end

defmodule A do
    import Pessoa, only: [say_my_name: 1] # except
    
    def c(name), do: say_my_name(name)
end

Structs

Structs definem um protocolo para um module. Nada mais é do que um Map com uma assinatura fixa.

defmodule B do
    defstruct name: nil, roles: []
    
    def add_role(b = %B{}, role), do: Map.put(b, :roles, b.roles ++ [role])
end

iex> b = %B{name: "Allan Batista", roles: ["nada"]}
%B{name: "Allan Batista", roles: ["nada"]}
iex> B.add_role(b, "casa")
%B{name: "Allan Batista", roles: ["nada", "casa"]}

Macros

TODO!!!

Pipe

Este recurso não esta disponível no python, é um recurso muito útil no Elixir, pois como tudo é passado como parâmetro para uma função que vai executar uma outra operação sobre os dados e em média os dados sempre passam por muitas funções, esse recurso adiciona um certo doce na linguagem.

No começo é meio estranho, mas conforme vai se acostumando com a sintexe, vai surgir um sentimento de falta desse recurso no python.

Exemplo

defmodule Pessoa do
  def fullname(%{firstname: firstname, lastname: lastname}) do
    [firstname, lastname]
    |> Enum.map(&String.capitalize/1)
    |> Enum.join(" ")
  end
end

Testando

iex> Pessoa.fullname(%{firstname: "allan", lastname: "batista"})
"Allan Batista"

Parallelismo

TODO

Referências

@diogommartins
Copy link

  • Vamos fazer um DOJO que envolva escrever algum exercício simples e testes
  • Ver Phoenix e HTTP
  • Ver testes com mock de depend6encia

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment