2 pontos por GN⁺ 2023-11-11 | 1 comentários | Compartilhar no WhatsApp
  • 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 with e 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/false e estados de erro internos em objetos, os retornos explícitos ok/error do 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
  • A expressão with do Elixir lembra o let de Haskell e Lisp, mas funciona como se tivesse um otherwise embutido
    • 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, false ou do estado interno de erro de um objeto
    • Por exemplo, model.update(params) retorna false em caso de falha e armazena o motivo em model.errors
    • Isso leva novamente ao problema do estado mutável
  • Um estilo como o das mônadas Result / Either, que explicita ok/error ou left/right no 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

 
GN⁺ 2023-11-11
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

    • Acho que você está se preocupando um pouco demais com a parte de concorrência. Basta entender o que é um processo e como send funciona
      GenServer é 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
    • Há uma necessidade real de levar Elixir para além da comunidade inicial, e sinto que isso já está acontecendo em alguma medida
      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
    • Eu sinto justamente o contrário. Depois que você aprende Elixir, ele fica menos mágico
      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
    • Vim de Ruby para Elixir e depois voltei para Node.js
      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
    • Não sei bem qual é a mágica do LiveView e do LiveBook
      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

    • Recentemente, mudamos de Rust para Elixir em um serviço novo, e esse ponto foi um dos motivos importantes
      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
    • Eu não quero muito que tarefas em segundo plano sejam fáceis desse jeito
      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

    • Fiquei curioso sobre Elixir e assisti a essa palestra no começo desta semana
      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

    • Fico curioso para saber que tipo de projeto foi mais divertido de trabalhar
  • 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”

    • Eu explico assim para a equipe quando nenhuma outra explicação funciona
      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

    • Em Elixir, esse problema é menor do que em outras linguagens dinâmicas como Ruby ou Python, ou em linguagens estáticas com mutabilidade
      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...
    • Tipos estão chegando. Espero que não demorem demais
      https://elixir-lang.org/blog/2023/06/22/type-system-updates-...
    • Aqui fico do lado oposto. Eu realmente gostaria que o GenServer do Elixir tivesse herança
      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
    • Nesse caso, talvez você goste de Gleam. Parece ter uma pegada parecida
  • 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 update do Rails, mas o jeito usado nos exemplos e generators padrão do Rails é a versão de update que não usa exceções

    • O exemplo mostra uma abordagem simples que limita a complexidade, especialmente para quem está aprendendo
      Quando 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
    • Como alguém que usa Ruby desde 2004, isso não me parece nada errado
      Muitas gems usam exceções para fluxo de controle, e isso também era comum nos apps e bibliotecas que escrevi ou mantive
    • Ruby e Rails não são a mesma coisa
      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 Python
  • Vim 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

    • A tipagem estática do Elixir está em desenvolvimento ativo
      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
    • Há muita coisa no próprio Elixir para gostar. Como o artigo diz, a sintaxe inspirada em Ruby é agradável de ler e fácil de aprender
      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
    • Olhando só para a linguagem, eu não chamaria de upgrade em relação a F#. É subjetivo, porém
      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
    • Usei bastante .NET e só usei F# ocasionalmente, mas Elixir tem alguns atrativos
      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
    • A falta de dicas de tipo nos parâmetros das funções é o maior golpe na produtividade em Elixir
      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

    • Sou o autor e conheço o conceito de período de lua de mel
      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
    • Antes de começar o doutorado, trabalhei profissionalmente como desenvolvedor Elixir por mais tempo do que isso
      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