Elixir - Uma breve introdução a linguagem por trás do Whatsapp, Nubank, Brex e tantas outras!
Nota do Autor: "Olá pessoal, o objetivo deste artigo, como o título sugere, não é apresentar todos os aspectos do paradigma da programação funcional, mas sim demonstrar a utilidade da linguagem Elixir e qual o seu diferencial em relação a outras linguagens de programação. Tive contato com essa linguagem na disciplina de Sistemas Distribuídos e, inicialmente, não fazia muito sentido para mim o professor abordar essa linguagem. No entanto, depois compreendi sua importância para sistemas que podem ser facilmente escaláveis e resilientes. Muitas empresas modernas utilizam a linguagem para a construção de sistemas com serviços críticos."
Introdução
Elixir é uma linguagem de programação funcional. Com linguagens funcionais como Elixir, podemos fazer melhor uso de nossos multinúcleos de CPU e escrever códigos mais curtos e explícitos. Para entender melhor a programação funcional, devo primeiro apresentar os seguintes princípios fundamentais: imutabilidade, funções e código declarativo.
Na programação funcional, todos os valores criados no programa são imutáveis. Por padrão, cada função possui um valor estável, o que significa que mecanismos de bloqueio não são necessários. Isso simplifica o trabalho paralelo. A imutabilidade está aparecendo mais nas linguagens de programação convencionais. Essas linguagens normalmente fornecem o mecanismo imutável, fornecendo uma alternativa de tipo de dados imutável ou um método para tornar um valor imutável.
A sintaxe Elixir compartilha muitas semelhanças com a sintaxe Ruby e é amplamente usada para construir aplicativos tolerantes a falhas, escaláveis e de fácil manutenção. A linguagem fornece escalabilidade, simultaneidade, tolerância a falhas e baixa latência.
A linguagem também possui um conjunto sólido de ferramentas de desenvolvimento web, como:
Mix: Mix é uma ferramenta de construção que permite criar projetos, executar testes, gerenciar tarefas e muito mais.
IEx: IEx, o shell interativo do Elixir, oferece muitos recursos como preenchimento automático, depuração, recarregamento de código e muito mais.
Phoenix: Phoenix é conhecido por ser um dos melhores frameworks web. É baseado na arquitetura MVC, assim como Ruby on Rails.
Importância do Elixir
Elixir é fundamental se você quiser construir um sistema que possa atuar de forma distribuída. Além disso, é uma ótima escolha para desenvolver microserviços. O Facebook Messenger inicialmente foi construído utilizando a base do Elixir, Erlang. No entanto, ele foi reescrito em outra linguagem anos depois. Mesmo assim, o WhatsApp ainda possui partes desenvolvidas com Erlang e Elixir.
O Nubank fez a compra de uma empresa chamada "Plataformatec", onde um dos fundadores é o criador da linguagem Elixir. O paradigma funcional também ajuda no processo de internacionalização do Nubank. "Como é um paradigma imutável, não precisamos mudar tudo desde o começo", afirma Bruno, o tech manager da Nubank. "A lógica do sistema financeiro de um país pode ser diferente do de outro, mas conseguimos usar a mesma base para todos".
A Brex, uma empresa de tecnologia em pagamentos bancários fundada por dois brasileiros, adotou o Elixir como uma forma de criar sistemas consistentes para operações de pagamento. Em um artigo escrito por um dos fundadores, ele pontuou o seguinte: "Depois de usar o Elixir por 18 meses, temos uma forte compreensão de como a plataforma se comporta no mundo real. Uma observação é que, apesar do Elixir ser uma linguagem relativamente de nicho, os novos contratados que nunca tiveram contato com o Elixir antes são produtivos em três semanas. Há uma quantidade razoável de livros e documentação disponíveis na linguagem que aceleram o processo de aprendizado
Uma breve introdução
Tipos Primitivos
Strings
Elixir usa o padrão UTF-8 para codificação de Strings. As Strings são iguais em qualquer outra linguagem. No exemplo abaixo temos uma saída de uma string no terminal.
IO.puts("Hello, World!")
Átomos
Átomos são constantes cujo o valor é são seus propios nomes. Em outras linguagens, como ruby por exemplo, Átomos são chamados de símbolos.
iex> :cat
:cat
iex> :dog
:dog
iex> :fish
:fish
Booleanos
Booleanos são valores que podem assumir verdadeiro ou falso
true
true==false
KeyWord List
Quando temos uma lista de tuplas e o primeiro item da tupla possui um átomo chamamos essa estrutura de Keyword List. Abaixo vemos um exemplo de KeyWord List
list = [{:c, 1}, {:d, 2}]
[c: 1, d: 2]
iex> list == [c: 1, d: 2]
true
Módulos e funções
No elixir as funções são agrupadas em módulos, no exemplo abaixo vemos chamadas de funções e uma função simples:
String.length("elixir")
defmodule Playground do
#Normal Function
def area(a, b) do
a * b
end
end
Importante ressaltar que no elixir não existe a palavra chave return, portanto, o retorno é baseado na saída da última linha. Embaixo tem um exemplo de função sem argumentos
# Function with no arguments
def run do
area(10,20)
end
Quando se tem um retorno de uma linha é possível fazer isso da seguinte maneira:
# Clean Way to do one line function
def area_of_circle(x, y) do: x * y
end
Funções Anonimas
Tal como o nome indica, uma função anônima não tem nome. Como vimos na lição Enum, elas são frequentemente passadas para outras funções. Para definir uma função anônima em Elixir nós precisamos das palavras-chave fn e end. Dentro destes, podemos definir qualquer número de parâmetros e corpos separados por ->.
# Anon Function
s = fn x -> x * x end
s.(2)
# Output: 4
sum = fn (a, b) -> a + b end
sum.(2, 3)
Pattern Matching
Pattern Matching é uma forma de associar uma expressão em elixir. Pattern Matching não pode ser limitado apenas a associação com variáveis, podemos executar isso com funções.
{name,age} = {"John", 25}
name #Output: "John"
age #Output: 25
Operador _ permite que você não faça associação diretas aos valores sendo bem útil quando você quer utilizar constantes. O operador _ também chamada de variável anonima
{_, x} = {11, "Hello"}
A utilidade de pattern matching com funções no permite criar múltiplas clausulas para a função. Elixir utiliza pattern matching para verificar todas as possíveis opções de match e identificar o primeiro conjunto de parâmetros associados para executar seu respectivo corpo.
handle_result = fn
{:ok, result} -> IO.puts "Handling result..."
{:ok, _} -> IO.puts "This would be never run as previous will be matched beforehand."
{:error} -> IO.puts "An error has occurred!"
end
Nesse código, associamos uma função anonima a uma variável que pode retornar três tipos de resultados diferentes conforme a entrada da tupla. Abaixo vemos um outro exemplo de pattern matching aplicado em um função nomeada
defmodule Geometry do
def area({:rectangle, a, b}) do
a * b
end
def area({:square, a}) do
a * a
end
def area(:circle, r) do
r * r * 3.14159
end
def area(unknown) do
{:error},{:unknow_shape, unknown}
end
end
Dê a você uma variável/valor, você pode querer
- Verifique se o tipo de dados corresponde ao tipo de dados esperado
- Verifique se a estrutura dos dados corresponde à estrutura de dados esperada
- Atribuir parte correspondente dos dados a uma variável
exemplos
verificar se os dados são mapas
%{} = params
podemos, por exemplo, fazer três verificações: verificar se os dados são um mapa, tem a chave email e o valor email é corresponde a um determinado input
%{"email" => "[email protected]"} = params
Atribuição de variáveis. Nesse caso, ele ira fazer a verificação se existe a chave email na tupla, se possuir, o valor da chave email irá ser atríbuida a variável my_email
%{"email" => my_email} = params
Podemos fazer o mesmo caso com variáveis anonimas.
%{"email" => _} = params
verificação do tipo struct
%User{} = params
Verifique se os dados são tuplas e têm valor específico
{:ok, data} = result
# you use this most of time
Funções nomeada
Nós podemos definir funções com nomes para referir a elas no futuro, estas funções nomeadas são definidas com a palavra-chave def dentro de um módulo. Nós iremos aprender mais sobre Módulos nas próximas lições, por agora nós iremos focar apenas nas funções nomeadas. Abaixo vemos um exemplo de função nomeada que retorna o tamanho de uma lista
defmodule Length do
def of([]), do: 0
def of([_ | tail]), do: 1 + of(tail)
end
Referências
https://news.ycombinator.com/item?id=21111662
https://medium.com/brexeng/why-brex-chose-elixir-fe1a4f313195
https://elixir-lang.org/docs.html
https://github.com/samsepiol1/study_elixir
Correção
Aparentemente a Brex resolveu mudar sua base de código de elixir para Kotlin, apesar de ainda reconhecer que elixir foi de grande importância para os estágio iniciais: https://medium.com/brexeng/building-backend-services-with-kotlin-7c8410795e4b