A mudança para Elixir
(leemeichin.com)- Um desenvolvedor backend Ruby entrou em uma empresa que usa Elixir como linguagem principal e conseguiu se adaptar relativamente rápido, mesmo sem experiência prévia com Elixir/Erlang, graças à sintaxe influenciada por Ruby
- Elixir oferece, como Ruby, diversão sintática e afinidade com DSLs, mas com um modelo funcional imutável sem classes, instâncias nem herança, reduzindo a carga de gerenciar estado
- Enquanto DSLs em Ruby dependem de definir dinamicamente objetos e métodos em tempo de execução, DSLs em Elixir usam macros em tempo de compilação para gerar código de runtime, revelando erros mais cedo
- A sintaxe de pipeline, a expressão
withe as tuplas Result no formato{:ok, result}são úteis para separar o fluxo normal do tratamento de erros recuperáveis - Em comparação com a abordagem do Ruby, que depende de exceções,
nil/falsee estados de erro internos em objetos, os retornos explícitosok/errordo Elixir parecem mais satisfatórios, e mesmo após 3 meses de uso a experiência de escrever código continua prazerosa
Como alguém com experiência em Ruby se adaptou ao Elixir
- No novo trabalho, a principal linguagem de backend é Elixir, e antes de começar a pessoa não tinha experiência escrevendo código em Elixir nem em Erlang
- Erlang não pareceu tão estranho por já ter tido algum contato com Prolog, e Elixir pôde ser aprendido rapidamente graças à sintaxe fortemente influenciada por Ruby
- Ainda não há domínio especializado de boas práticas, arquitetura ou conceitos mais baixos de Erlang, mas isso não impediu a entrada no Elixir
- Elixir é visto como uma linguagem que mantém a diversão de programar que Ruby oferece, ao mesmo tempo em que reduz armadilhas relacionadas a estado comuns em linguagens orientadas a objetos
- Não há classes, instâncias nem herança
- Baseia-se em imutabilidade e estilo funcional
- Não fica presa a um sistema de tipos estático
- Código com estado mutável é muito mais difícil de manter, e nem sempre fica claro se o estado é local à função, à instância ou à classe
- Em Ruby, tudo é objeto, então cada objeto pode ter estado, e ao tentar criar DSLs intuitivas é fácil misturar estados em nível de função, instância e classe
- A Eigenclass em Ruby é um conceito como a classe da classe, e uma instância pode modificar a eigenclass de modo a afetar também o estado de outras instâncias
A diferença entre DSLs e macros em tempo de compilação
- Elixir também oferece suporte a DSLs e tem sintaxe familiar para quem vem de Ruby, mas a diferença principal está no momento em que a DSL é criada
- DSLs em Elixir são macros em tempo de compilação e geram código de runtime
- DSLs em Ruby dependem de modificar o runtime para definir dinamicamente objetos e métodos
- Se uma macro em Elixir for usada incorretamente, o erro aparece na biblioteca; se o código gerado pela macro estiver errado, surge um erro do compilador
- Como esses erros aparecem em tempo de compilação, os testes podem se concentrar mais na lógica real da aplicação
O fluxo criado por pipelines e with
- O operador de pipeline
|>do Elixir é útil, mas os operadores de threading do Clojure também têm vantagens- O
->do Clojure, assim como o|>do Elixir, passa o resultado da expressão anterior como primeiro argumento da função seguinte - O
->>do Clojure passa o resultado anterior como último argumento da função seguinte - Essa diferença parece pequena, mas é útil ao escrever código de interoperabilidade sem recorrer a funções anônimas
- O
- A expressão
withdo Elixir lembra oletde Haskell e Lisp, mas funciona como se tivesse umotherwiseembutido- Ela permite dividir funções complexas entre o caminho normal na parte de cima e o tratamento de erros na parte de baixo
- Se não for possível fazer pattern matching em
{:ok, result}, isso pode ser tratado como um erro recuperável, o que combina bem com as tuplas Result
As vantagens dos retornos explícitos de erro
- Em Ruby, é comum usar exceções no fluxo de controle e, em vez de tipos de erro explícitos, muitas vezes depender de um resultado de sucesso,
nil,falseou do estado interno de erro de um objeto- Por exemplo,
model.update(params)retornafalseem caso de falha e armazena o motivo emmodel.errors - Isso leva novamente ao problema do estado mutável
- Por exemplo,
- Um estilo como o das mônadas
Result/Either, que explicitaok/errorouleft/rightno valor de retorno, parece mais satisfatório do que o fluxo baseado em exceções do Ruby - Mesmo após 3 meses usando Elixir, ele continua sendo uma linguagem que mantém viva a sensação que Ruby transmitia de que “programar é divertido”
1 comentários
Comentários do Hacker News
Nas últimas semanas, tenho visto vídeos de Elixir no YouTube quase todos os dias
Parece que houve uma conferência de Elixir há pouco tempo e, depois de assistir a alguns vídeos, o YouTube continuou me recomendando conteúdo sobre Elixir
Gosto das ideias do Erlang, mas quando lidei no passado com coisas como o servidor de chat ejabberd, achei tudo muito peculiar
Sempre que vejo vídeos do Joe Armstrong, penso que ele foi um gênio subestimado, e processos isolados com passagem de mensagens, ou seja, o modelo de atores, parecem uma boa candidata ao futuro da programação distribuída
Também achei interessante que, no vídeo da nova linguagem Misty do Crockford https://www.youtube.com/watch?v=R2idkNdKqpQ, a pergunta tenha ido na direção de Elixir/Erlang
Dito isso, quando vejo a “mágica” do LiveView e do LiveBook, ainda não estou 100% convencido. Coisas que parecem boas demais quase sempre tinham uma pegadinha, e termos pouco familiares como GenServer e conhecimento específico da plataforma dão a sensação de entrar em um portal de mão única; não sei se é conhecimento transferível para outros lugares
Como Go também tem uma história de concorrência bastante boa, uma linguagem e plataforma com uma comunidade pequena traz riscos grandes em contratação, bibliotecas e materiais. Ainda assim, o que a comunidade Elixir realizou é realmente impressionante, e também estou curioso para ver como a implementação do sistema de tipos vai evoluir
sendfuncionaGenServer é uma generalização natural de um padrão que você acabaria escrevendo manualmente inúmeras vezes, e o conhecimento sobre atores pode ser levado para outros lugares
Também há bibliotecas em Go que implementam a abstração de atores, e a caixa de correio de um processo pode ser vista como uma fila de mensagens, como os canais de Go. A diferença está mais em como o interpretador e a cessão de tempo ao processador funcionam
Já o LiveView realmente parece mágica. De fato, há muitas partes móveis se encaixando, e ele é resultado de trabalho acumulado por várias comunidades ao longo dos últimos 10 anos; há muitas abstrações, mas as ideias em si são maduras
Coisas como Riak estavam à frente de seu tempo, e quase tinham a ideia de que seria possível construir sistemas distribuídos robustos como se constrói um GenServer
Tanto Chris quanto José têm se movimentado bastante para aumentar a exposição fora do círculo inicial de Elixir, e as pessoas dentro da comunidade também fazem o mesmo em seus respectivos níveis
Contratar não é tão complicado quanto parece. Há bastante gente que quer trabalhar com essa linguagem, e bons desenvolvedores pegam o ritmo bem rápido
Ainda assim, é preciso domar o fato de ela ser uma linguagem de nicho. Isso é um risco para a linguagem em si, mas no curto prazo também pode virar uma recompensa por dominar uma tecnologia de nicho
Dá para ver e entender o que a maior parte das coisas faz e ler o código. Com JavaScript, muitas vezes não é assim
É verdade que contratar e encontrar tutoriais é difícil. Por exemplo, encontrar alguém que já tenha feito em Elixir o que eu quero fazer é muito mais difícil do que em JavaScript
Bibliotecas não foram um grande problema, e há bastante coisa. Ao lidar com APIs, às vezes há bibliotecas cliente para JavaScript ou Python, mas não para Elixir; normalmente, porém, usar a API apenas com um cliente HTTP não é tão difícil
Uso Node.js hoje não porque eu queira, mas porque preciso. Depois de usar, penso que jamais começaria um projeto novo em Node.js
As ferramentas de controle de concorrência, escalabilidade, confiabilidade e observabilidade do Node.js ficam muito atrás do ecossistema BEAM
Acho que, quando as pessoas sentem que Elixir parece bom demais para acreditar, às vezes é porque estão acostumadas demais a plataformas que não foram projetadas para resiliência
No LiveBook, exceto pelos detalhes exatos do cálculo de diferenças ou da compressão de dados, é bem fácil ter um modelo mental correto. Se estiver com pressa, dá até para olhar as mensagens WebSocket e entender razoavelmente o que está acontecendo
LiveView é surpreendentemente intuitivo
Um grande atrativo do Elixir/Erlang é que, no contexto de servidores web, é realmente fácil rodar tarefas em segundo plano, sem se preocupar que E/S bloqueante pare o servidor inteiro
Em um emprego anterior, eu precisava fazer muitas requisições HTTP dentro de um handler de webhooks, e quando ocorriam muitas ao mesmo tempo, todos os processos do sistema operacional ficavam ocupados e o site inteiro caía
Nessas horas, eu pensava com muita vontade que, se fosse Elixir, bastaria iniciar uma Task e seguir em frente
A ideia de compartilhar objetos entre processos assíncronos do Tokio parecia difícil em comparação com um recurso de primeira classe do Elixir. Não quer dizer que seja impossível ou inerentemente difícil, mas parecia mais difícil fazer corretamente
Quero colocar tarefas em segundo plano em uma capacidade computacional diferente das requisições HTTP. O padrão de uso de recursos é diferente, e quero ter estado ou filas na frente para deixar explícitos retentativas, tratamento de erros e contrapressão
Claro que seria ótimo se não houvesse essa complexidade e o mesmo framework cuidasse de tudo, mas acho difícil chegar a esse ponto apenas com uma linguagem de programação ou runtime. Monitoramento e procedimentos operacionais também precisam fazer parte da solução
A palestra The Soul of Erlang and Elixir, de Sasa Juric, mostra bem o quão poderosa essa linguagem pode ser
https://www.youtube.com/watch?v=JvBT4XBdoUE&t=4
Recomendo muito para quem já ouviu coisas como “deixe falhar” ou “a BEAM faz concorrência do jeito certo”, mas ainda não entendeu bem. É uma palestra realmente excelente
Desde 1991, venho programando todos os dias em Erlang, e pretendia continuar assim
Nesse meio-tempo, também experimentei Haskell, Rust, Elixir etc. Tipos fortes têm muito valor, mas no fim tudo se resume a onde você se diverte mais
Erlang é a linguagem de programação que me dá mais prazer, e pretendo continuar hackeando em Erlang pelo menos até me aposentar daqui a alguns anos
Conheci Elixir há pouco mais de um ano, e foi bem divertido aprender e colocá-lo em vários componentes
A maior parte da diversão vem do fato de que o BEAM/OTP já traz muita coisa pronta por padrão
Alguém da equipe com quem eu trabalhava certa vez disse: “BEAM/OTP é como k8s, só que sem as partes complicadas”
Passei alguns anos aprendendo Elixir, mas uma migração para OpenShift, ou seja, k8s, que projetei recentemente acabou diluindo um pouco essa vantagem
Sem k8s, teria sido uma proposta muito mais atraente. Em vez disso, mantivemos a stack de desenvolvimento existente e alcançamos com conceitos de k8s aquilo que teríamos feito com OTP. Claro que os trade-offs foram diferentes
“Elixir entrega uma linguagem divertida como Ruby, mas tira as minas terrestres relacionadas a estado das linguagens orientadas a objetos. Não há classes, instâncias nem herança; é imutável, funcional e não fica preso a um sistema de tipos estático”
Eu quero isso, mas também gostaria que tivesse tipos. Em codebases grandes, estou convencido de que tipos fortes são o caminho certo, porque expõem muita mágica e facilitam o raciocínio
Graças ao pattern matching/unificação nas funções, dá para saber razoavelmente bem qual é a forma dos dados quando eles entram e saem. Mesmo assim, se bater insegurança, existe o Dialyzer
Dialyzer é uma ferramenta de análise estática para Erlang/Elixir. Ela vem incluída na distribuição padrão do Erlang e é uma sigla para “DIscrepancy AnaLYZer for ERlang programs”
Ela encontra inconsistências como erros de tipo, código morto e testes desnecessários em um único módulo Erlang ou em um conjunto de aplicações. Infere tipos com base em programas corretos, e anotações de tipo não são obrigatórias, mas, quando existem, fornecem avisos melhores
https://fly.io/phoenix-files/adding-dialyzer-without-the-pai...
https://elixir-lang.org/blog/2023/06/22/type-system-updates-...
GenServer é mais próximo de um objeto e interage por mensagens, não é simplesmente uma struct com funções associadas
Seria bom poder ter um template básico para os GenServers do projeto, com registro comum, configuração de memória, logging etc., e especializá-lo para três variações de tarefas específicas
Elixir cobre essa lacuna com geração de código via macros, mas não é lá muito bom. Meus GenServers ficam cheios de repetição trivial
Acho simplesmente errada a afirmação de que “em Ruby é comum usar exceções para fluxo de controle”
O exemplo depois desse parágrafo é o método
updatedo Rails, mas o jeito usado nos exemplos e generators padrão do Rails é a versão deupdateque não usa exceçõesQuando uma biblioteca ou aplicação de certo porte fica complexa o suficiente, ela passa a usar classes de erro descritivas. Isso porque tratamento de erro baseado em estado, como no ActiveRecord, é doloroso
Como Ruby agora tem algum grau de pattern matching, acho que isso pode substituir exceções para fluxo de controle. Em vez de lançar uma exceção, retorna-se a própria classe e faz-se matching nela
Muitas gems usam exceções para fluxo de controle, e isso também era comum nos apps e bibliotecas que escrevi ou mantive
O Rails tem muitos métodos
bang!que lançam exceções em caso de erro. Mas, de modo geral, em Ruby é realmente comum usar exceções para fluxo de controle, como em PythonVim do F# e mexi só um pouco com Elixir, mas infelizmente a linguagem parece um pouco confusa
Por exemplo, a assinatura de uma função mostra apenas o nome e a quantidade de parâmetros, mas não os tipos, então em bibliotecas grandes fica bem difícil entender mentalmente o que entra onde
Muitas vezes só fica claro que você passou um parâmetro errado depois de executar o código e depurar. Em linguagens de tipagem rigorosa como F#, esse tipo de problema é detectado durante a codificação, o que reduz muito tempo e esforço
Por isso, fora o ambiente Erlang, fico curioso para saber qual é o principal atrativo do Elixir
https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...
No momento, ele é de fato uma linguagem dinâmica, então não se obtém muita informação de tipos da própria linguagem. Ainda não é uma linguagem de tipagem estática
Mas o que realmente coloca Elixir acima de outras linguagens que usei é o BEAM/OTP
Pela minha experiência, muitos recursos que em outras linguagens você teria de trazer como bibliotecas já vêm incluídos por padrão. Essas bibliotecas aumentam bastante a complexidade e, às vezes, também têm uma curva de aprendizado grande. Recursos relacionados a assíncrono são um bom exemplo
Acho que, depois de usar Elixir por um tempo e aprender essas ferramentas e abstrações embutidas, você passa a entender a força de todo o ecossistema
O principal atrativo provavelmente é rodar sobre o BEAM com uma sintaxe com cara de Ruby. Acho que o BEAM é uma vantagem bem grande
Não se compara bem aos tipos do F#, mas dá para usar typespec: https://hexdocs.pm/elixir/1.15.7/typespecs.html
Por exemplo, há um fluxo em que machine learning é integrado nativamente com Axon, Nx e BumbleBee, podendo até compilar para GPU: https://www.youtube.com/watch?v=HK38-HIK6NA
A comunidade está realmente impulsionando bastante essa área
O modo como o LiveView funciona também é atraente. Dito isso, no .NET existe o Blazor
Elixir pode ser usado em vários contextos, como Nerves para embarcados ou scripting. Pelo que sei, F# também pode ser usado para scripting
No geral, o BEAM e as estruturas relacionadas são pontos fortes, e o gerenciador de pacotes Hex também é muito bom. Claro que o .NET também tem boas ferramentas
Dá para se virar com typespec, pattern matching e guards, mas é bem trabalhoso e, pelo menos no plugin da JetBrains mantido pela comunidade, não é amigável a IDE/IntelliSense
Seria bom escrever sobre isso depois de usar por 2 anos, não logo no começo
Algo novo sempre parece, no início, uma solução universal brilhante e sem defeitos
Primeiras impressões também têm seu lugar, e é isso que estou oferecendo. O público não são especialistas em Elixir/Erlang, mas especialistas em Ruby
Elixir era uma joia naquela época e continua sendo. Não é perfeito, mas se sustenta surpreendentemente bem
Uso Ruby desde mais ou menos 2007
Toda vez que tentei migrar para a próxima coisa “legal”, acabei voltando para Ruby, porque gosto de Ruby, consigo trabalhar mais rápido com Ruby, e era isso
Mas ultimamente tenho visto muito mais gente falando de Elixir, e gosto da aparência e da sensação dele. Em breve vou começar um novo projeto paralelo e acho que preciso experimentar