2 points par GN⁺ 2023-11-11 | 1 commentaires | Partager sur WhatsApp
  • Après avoir rejoint une entreprise dont Elixir est le langage principal côté backend, un développeur backend Ruby s’est adapté assez rapidement, même sans expérience d’Elixir/Erlang, grâce à une syntaxe influencée par Ruby
  • Elixir offre, comme Ruby, un plaisir syntaxique et une affinité avec les DSL, tout en réduisant la charge de gestion de l’état grâce à un modèle fonctionnel immuable sans classes, instances ni héritage
  • Alors que les DSL Ruby s’appuient sur la définition dynamique d’objets et de méthodes à l’exécution, les DSL Elixir génèrent du code d’exécution via des macros à la compilation, ce qui fait apparaître les erreurs plus tôt
  • La syntaxe de pipeline, l’expression with et les tuples Result de la forme {:ok, result} sont utiles pour séparer le flux normal du traitement des erreurs récupérables
  • Par rapport à l’approche Ruby reposant sur les exceptions, nil/false et l’état d’erreur interne des objets, les retours explicites ok/error d’Elixir semblent plus satisfaisants, et l’expérience d’écriture reste agréable après trois mois d’utilisation

Comment un développeur Ruby s’est adapté à Elixir

  • Le nouveau poste utilise Elixir comme principal langage backend, alors qu’avant de commencer il n’avait jamais écrit de code Elixir ni Erlang
  • Erlang ne lui était pas trop étranger grâce à une petite expérience avec Prolog, et Elixir a pu être appris rapidement grâce à une syntaxe fortement influencée par Ruby
  • Il n’est pas encore expert des bonnes pratiques, de l’architecture ni des concepts Erlang de plus bas niveau, mais cela n’a pas empêché d’entrer dans Elixir
  • Elixir est perçu comme un langage qui conserve le plaisir de programmer qu’offre Ruby, tout en réduisant les pièges liés à l’état fréquents dans les langages orientés objet
    • Pas de classes, d’instances ni d’héritage
    • Fondé sur l’immuabilité et un style fonctionnel
    • Pas lié à un système de types statique
  • Le code avec état mutable est beaucoup plus difficile à maintenir, et il n’est pas toujours clair si l’état est local à une fonction, à une instance ou à une classe
  • En Ruby, tout est objet, chaque objet peut donc avoir son propre état, et lorsqu’on cherche à créer des DSL intuitifs, les états au niveau des fonctions, des instances et des classes peuvent facilement se mélanger
  • L’eigenclass de Ruby est un concept proche d’une classe de classe, et une instance peut modifier son eigenclass de manière à affecter aussi l’état d’autres instances

DSL et différence avec les macros à la compilation

  • Elixir prend aussi en charge les DSL et sa syntaxe est familière, comme Ruby, mais la différence essentielle tient au moment où le DSL est créé
    • Les DSL Elixir sont des macros à la compilation qui génèrent du code d’exécution
    • Les DSL Ruby reposent sur la modification du runtime pour définir dynamiquement objets et méthodes
  • Si une macro Elixir est mal utilisée, l’erreur se produit dans la bibliothèque, et si le code de macro généré est incorrect, une erreur de compilation survient
    • Comme ces erreurs apparaissent à la compilation, les tests peuvent se concentrer davantage sur la logique applicative réelle

Le flux créé par les pipelines et with

  • L’opérateur de pipeline |> d’Elixir est utile, mais les opérateurs de threading de Clojure ont aussi leurs avantages
    • -> en Clojure place le résultat de l’expression précédente comme premier argument de la fonction suivante, comme |> en Elixir
    • ->> en Clojure place le résultat précédent comme dernier argument de la fonction suivante
    • Même si cette différence semble minime, elle est utile pour écrire du code d’interopérabilité sans recourir à des fonctions anonymes
  • L’expression with d’Elixir rappelle le let de Haskell ou Lisp, tout en se comportant comme si un otherwise était intégré
    • Elle permet de diviser une fonction complexe entre le chemin nominal en haut et la gestion des erreurs en bas
    • Si le pattern matching sur {:ok, result} échoue, cela peut être traité comme une erreur récupérable, ce qui s’accorde bien avec les tuples Result

Les avantages des retours d’erreur explicites

  • En Ruby, il est courant d’utiliser les exceptions pour le flux de contrôle, et de s’appuyer sur un résultat de succès, nil, false ou un état d’erreur interne à l’objet plutôt que sur un type d’erreur explicite
    • Par exemple, model.update(params) renvoie false en cas d’échec et stocke la raison de l’échec dans model.errors
    • Cela ramène au problème de l’état mutable
  • Une approche consistant à expliciter dans les valeurs de retour ok/error ou left/right, comme avec les monades Result / Either, paraît plus satisfaisante que le flux d’exceptions à la Ruby
  • Après trois mois d’utilisation d’Elixir, Elixir reste un langage qui prolonge le sentiment montré par Ruby : « programmer, c’est amusant »

1 commentaires

 
GN⁺ 2023-11-11
Avis Hacker News
  • Ces dernières semaines, je regarde presque tous les jours des vidéos YouTube sur Elixir
    Il semble qu’il y ait eu une conférence Elixir il y a peu, et après en avoir regardé quelques-unes, YouTube continue de me recommander du contenu Elixir
    J’aime les idées d’Erlang, mais quand j’ai dû manipuler des choses comme le serveur de chat ejabberd par le passé, ça m’avait paru trop particulier
    Chaque fois que je regarde des vidéos de Joe Armstrong, je me dis que c’était un génie sous-estimé, et les processus isolés avec passage de messages, autrement dit le modèle d’acteurs, me semblent être un bon candidat pour l’avenir de la programmation distribuée
    J’ai aussi été frappé de voir que, dans la vidéo sur le nouveau langage Misty de Crockford https://www.youtube.com/watch?v=R2idkNdKqpQ, les questions se sont orientées vers Elixir/Erlang
    Cela dit, la « magie » de LiveView et LiveBook ne me convainc pas encore à 100 %. Ce qui paraît trop beau a généralement un piège, et les termes peu familiers comme GenServer ainsi que les connaissances propres à la plateforme donnent l’impression d’entrer dans un portail à sens unique ; je ne sais pas si ce sont des connaissances transférables ailleurs
    Alors que Go a lui aussi une histoire assez solide côté concurrence, un langage et une plateforme à petite communauté comportent de gros risques en matière de recrutement, de bibliothèques et de ressources. Malgré tout, ce que la communauté Elixir a accompli est vraiment impressionnant, et j’ai hâte de voir comment l’implémentation du système de types évoluera

    • Je pense que tu t’inquiètes un peu trop sur la concurrence. Il suffit de comprendre ce qu’est un processus et comment fonctionne send
      GenServer est une généralisation naturelle d’un motif que tu finiras par écrire toi-même un nombre incalculable de fois, et les connaissances sur les acteurs se transfèrent ailleurs
      Il existe aussi des bibliothèques qui implémentent une abstraction d’acteurs en Go, et la boîte aux lettres d’un processus peut se voir comme une file de messages, à la manière des canaux de Go. La différence se situe plutôt dans la façon dont l’interpréteur et le yield au processeur fonctionnent
      En revanche, LiveView a bien un côté magique. En réalité, beaucoup de pièces mobiles s’imbriquent, et c’est le résultat de dix ans de travail accumulé dans plusieurs communautés ; il y a beaucoup d’abstractions, mais les idées elles-mêmes sont mûres
      Des choses comme Riak étaient en avance sur leur temps, et portaient presque déjà l’idée qu’on pouvait construire des systèmes distribués robustes comme on construit un GenServer
    • Il y a un vrai besoin de pousser Elixir au-delà de sa communauté initiale, et j’ai l’impression que cela se produit déjà dans une certaine mesure
      Chris et José travaillent tous deux beaucoup à accroître l’exposition en dehors du cercle initial d’Elixir, et les membres de la communauté font la même chose à leur niveau
      Le recrutement n’est pas aussi compliqué qu’on pourrait le croire. Il y a pas mal de personnes qui veulent travailler avec ce langage, et les bons développeurs rattrapent leur retard assez vite
      Cela dit, il faut apprivoiser le côté langage de niche. C’est un risque pour le langage lui-même, mais à court terme, être compétent dans une technologie de niche peut aussi être récompensé
    • J’ai plutôt l’impression inverse. Une fois qu’on a appris Elixir, il y a moins de magie
      On peut voir et comprendre ce que fait la plupart du code, et le lire. Avec JavaScript, ce n’est pas souvent le cas
      En revanche, il est vrai que recruter et trouver des tutoriels est difficile. Par exemple, trouver quelqu’un qui a déjà fait en Elixir ce que je veux faire est beaucoup plus difficile qu’en JavaScript
      Les bibliothèques n’ont pas vraiment été un gros problème, et il y en a pas mal. Quand on utilise des API, il existe parfois des bibliothèques clientes pour JavaScript ou Python, mais pas pour Elixir ; en général, utiliser l’API avec un simple client HTTP n’est pas si difficile
    • Je suis passé de Ruby à Elixir, puis revenu à Node.js
      Si j’utilise Node.js aujourd’hui, ce n’est pas parce que j’en ai envie, mais parce que je dois le faire. Après l’avoir essayé, je me suis dit que je ne lancerais absolument jamais un nouveau projet en Node.js
      Les outils de contrôle de la concurrence, de montée en charge, de fiabilité et d’observabilité de Node.js sont très loin derrière l’écosystème BEAM
      À mon avis, si certains trouvent Elixir trop beau pour être crédible, c’est parfois parce qu’ils sont trop habitués à des plateformes qui n’ont pas été conçues pour la résilience
    • Je ne vois pas bien en quoi LiveView et LiveBook seraient magiques
      Avec LiveBook, hormis les détails exacts du calcul de diff ou de la compression des données, il est assez facile d’avoir le bon modèle mental. En cas d’urgence, on peut regarder les messages WebSocket et comprendre assez bien ce qui se passe
      LiveView est étonnamment intuitif
  • Le grand attrait d’Elixir/Erlang, dans le contexte d’un serveur web, c’est qu’il permet de lancer des tâches en arrière-plan très facilement, sans craindre qu’une entrée-sortie bloquante fige tout le serveur
    Dans un précédent poste, je devais faire beaucoup de requêtes HTTP dans un gestionnaire de webhooks, et quand il y en avait suffisamment en parallèle, tous les processus du système d’exploitation se retrouvaient occupés et tout le site tombait
    Dans ces moments-là, je me disais vraiment que, si on avait été en Elixir, il aurait suffi de lancer une Task et de passer à la suite

    • Nous sommes récemment passés de Rust à Elixir pour un nouveau service, et ce point a été une raison assez importante
      L’idée de partager des objets entre processus asynchrones Tokio m’a paru difficile comparée au fait que ce soit une fonctionnalité de premier ordre dans Elixir. Ce n’est pas impossible ni intrinsèquement difficile, mais ça semble plus difficile à faire correctement
    • Je ne suis pas très fan de cette façon de rendre les tâches en arrière-plan faciles
      Je préfère placer les tâches en arrière-plan sur une capacité de calcul différente de celle des requêtes HTTP. Les profils d’utilisation des ressources sont différents, et je veux mettre un état ou une file en amont pour rendre explicites les reprises, la gestion des erreurs et la contre-pression
      Bien sûr, ce serait agréable de ne pas avoir cette complexité et que le même framework s’occupe de tout, mais je pense qu’un langage de programmation ou un runtime ne peut pas aller jusque-là à lui seul. Le monitoring et les procédures d’exploitation doivent aussi faire partie de la solution
  • La présentation The Soul of Erlang and Elixir de Sasa Juric montre bien à quel point ce langage peut être puissant
    https://www.youtube.com/watch?v=JvBT4XBdoUE&t=4

    • Curieux à propos d’Elixir, j’ai regardé cette présentation au début de la semaine
      Je la recommande vivement à ceux qui ont entendu « laissez-le échouer » ou « BEAM gère vraiment bien la concurrence », mais qui n’ont pas encore eu le déclic. C’est une présentation vraiment formidable
  • Depuis 1991, je programme tous les jours en Erlang, et je comptais continuer ainsi
    Entre-temps, j’ai aussi essayé Haskell, Rust, Elixir, etc. Le typage fort a beaucoup de valeur, mais au final tout se ramène à l’endroit où l’on prend le plus de plaisir
    Erlang est le langage de programmation qui m’apporte le plus de plaisir, et je prévois de continuer à hacker en Erlang au moins jusqu’à ma retraite, dans quelques années

    • Je suis curieux de savoir quels types de projets ont été les plus amusants à mener
  • Cela fait un peu plus d’un an que j’ai découvert Elixir, et j’ai vraiment pris plaisir à l’apprendre et à l’intégrer dans plusieurs composants
    Le plus grand plaisir vient du fait que BEAM/OTP fournit énormément de choses par défaut à lui seul
    Quelqu’un de l’équipe avec laquelle je travaillais a dit un jour : « BEAM/OTP, c’est comme k8s, sans les parties compliquées »

    • C’est ainsi que je l’explique à l’équipe quand les autres explications ne passent pas
      J’ai appris Elixir pendant plusieurs années, mais la migration OpenShift — donc k8s — que j’ai récemment conçue a quelque peu atténué cet avantage
      Sans k8s, la proposition aurait été bien plus séduisante. À la place, nous avons conservé la stack de développement existante et atteint, avec des concepts k8s, ce que nous aurions fait avec OTP. Les compromis étaient évidemment différents
  • « Elixir vous donne un langage plaisant comme Ruby, tout en retirant les pièges liés à l’état des langages orientés objet. Pas de classes, pas d’instances, pas d’héritage ; il est immuable, fonctionnel, et ne vous enferme pas dans un système de typage statique »
    C’est ce que je veux, mais j’aimerais aussi avoir des types. Je suis convaincu que le typage fort est la bonne approche dans les grandes bases de code, car il met en évidence beaucoup de magie et rend le raisonnement plus facile

    • En Elixir, ce problème se pose moins que dans d’autres langages dynamiques comme Ruby ou Python, ou que dans des langages statiques avec mutabilité
      Grâce au pattern matching / à l’unification des fonctions, on sait assez bien quelle forme prennent les données qui entrent et sortent. Et si l’on reste inquiet, il y a Dialyzer
      Dialyzer est un outil d’analyse statique pour Erlang/Elixir. Il est inclus dans la distribution standard d’Erlang, et son nom signifie « DIscrepancy AnaLYZer for ERlang programs »
      Il détecte, dans un module Erlang isolé ou un ensemble d’applications, des incohérences comme des erreurs de type, du code mort ou des tests inutiles. Il infère les types à partir de programmes corrects ; les annotations de type ne sont pas obligatoires, mais lorsqu’elles sont présentes, elles permettent de produire de meilleurs avertissements
      https://fly.io/phoenix-files/adding-dialyzer-without-the-pai...
    • Les types arrivent. J’espère que ce ne sera pas dans trop longtemps
      https://elixir-lang.org/blog/2023/06/22/type-system-updates-...
    • Là-dessus, je suis dans le camp opposé. J’aimerais vraiment que les GenServer d’Elixir aient de l’héritage
      Un GenServer ressemble à un objet : il ne s’agit pas simplement d’une structure avec des fonctions associées, il interagit via des messages
      J’aimerais pouvoir définir, pour les GenServer d’un projet, un template de base avec de l’enregistrement commun, de la configuration mémoire, du logging, etc., puis le spécialiser pour trois variantes de tâches précises
      Elixir comble ce manque avec de la génération de code par macros, mais ce n’est pas terrible. Mes GenServer sont remplis de petites répétitions
    • Dans ce cas, Gleam pourrait te plaire. Il semble avoir une sensation assez similaire
  • Dire qu’« en Ruby, il est courant d’utiliser les exceptions pour le flux de contrôle » me semble tout simplement faux
    L’exemple qui suit ce paragraphe est la méthode update de Rails, alors que la façon utilisée dans les exemples Rails standard et les générateurs est la version de update qui n’utilise pas d’exception

    • Les exemples montrent surtout une approche simple qui limite la complexité, en particulier pour les personnes qui apprennent
      Les bibliothèques ou applications d’une certaine taille, lorsqu’elles deviennent suffisamment complexes, passent à des classes d’erreurs explicites. Car la gestion d’erreurs basée sur l’état, comme dans ActiveRecord, est pénible
      Maintenant que Ruby dispose dans une certaine mesure du pattern matching, je pense qu’il peut remplacer les exceptions utilisées pour le flux de contrôle. Au lieu de lever une exception, on peut retourner la classe elle-même et faire un matching dessus
    • En tant qu’utilisateur de Ruby depuis 2004, cela ne me semble pas faux du tout
      Beaucoup de gems utilisent les exceptions pour le flux de contrôle, et c’était courant aussi dans les apps et bibliothèques que j’ai écrites ou maintenues
    • Ruby et Rails ne sont pas la même chose
      Rails comporte beaucoup de méthodes bang! qui lèvent une exception en cas d’erreur. Mais, de manière générale, en Ruby comme en Python, il est effectivement courant d’utiliser des exceptions pour le flux de contrôle
  • Je viens de F# et je n’ai fait qu’effleurer Elixir, mais le langage me semble malheureusement un peu déroutant
    Par exemple, les signatures de fonctions n’exposent que le nom et le nombre de paramètres, pas les types ; dans une grosse bibliothèque, il devient donc assez difficile de comprendre mentalement ce qui va où
    On ne voit souvent qu’on a passé de mauvais paramètres qu’après avoir exécuté le code et débogué. Dans un langage à typage strict comme F#, ce genre de problème est détecté directement pendant le codage, ce qui réduit beaucoup le temps et les efforts
    Du coup, en dehors de l’environnement Erlang, je me demande quel est l’attrait principal d’Elixir

    • Le typage statique d’Elixir est en plein développement
      https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...
      Pour l’instant, c’est bien un langage dynamique, donc on n’obtient pas beaucoup d’informations de type depuis le langage lui-même. Ce n’est pas encore un langage à typage statique
    • Il y a beaucoup de choses à aimer dans Elixir lui-même. Comme le dit l’article, sa syntaxe inspirée de Ruby est agréable à lire et facile à apprendre
      Mais ce qui, pour moi, donne réellement l’avantage à Elixir par rapport aux autres langages que j’ai utilisés, c’est BEAM/OTP
      D’après mon expérience, beaucoup de fonctionnalités qu’il faudrait apporter via des bibliothèques dans d’autres langages sont incluses par défaut. Ces bibliothèques augmentent fortement la complexité et ont parfois une courbe d’apprentissage importante. Les fonctionnalités liées à l’asynchrone en sont un bon exemple
      Je pense qu’après avoir utilisé Elixir un certain temps et s’être familiarisé avec ces outils et abstractions intégrés, on comprend la puissance de tout l’écosystème
    • Si l’on ne considère que le langage, je ne dirais pas que c’est une amélioration par rapport à F#. C’est subjectif, bien sûr
      L’attrait principal est probablement le fait qu’il tourne sur BEAM avec une syntaxe à la Ruby. BEAM est à mon avis un avantage assez important
      Ce n’est pas vraiment comparable aux types de F#, mais on peut utiliser les typespecs : https://hexdocs.pm/elixir/1.15.7/typespecs.html
    • J’ai pas mal utilisé .NET et seulement occasionnellement F#, mais Elixir a plusieurs attraits
      Par exemple, il existe un flux où le machine learning est intégré nativement avec Axon, Nx et BumbleBee, jusqu’à la compilation GPU : https://www.youtube.com/watch?v=HK38-HIK6NA
      La communauté pousse réellement très fort dans cette direction
      Le fonctionnement de LiveView est aussi séduisant. Cela dit, .NET a Blazor
      Elixir peut être utilisé dans divers contextes, comme l’embarqué avec Nerves ou le scripting. À ma connaissance, F# peut aussi servir au scripting
      Globalement, BEAM et les structures qui l’entourent sont ses points forts, et le gestionnaire de paquets Hex est vraiment bon. Bien sûr, .NET a aussi de bons outils
    • L’absence de type hints sur les paramètres de fonctions est ce qui nuit le plus à la productivité dans Elixir
      On peut s’en sortir avec les typespecs, le pattern matching et les guards, mais c’est assez fastidieux et, au moins dans le plugin JetBrains porté par la communauté, ce n’est pas très convivial pour l’IDE/IntelliSense
  • Il vaudrait mieux écrire ça après l’avoir utilisé pendant 2 ans, pas au moment de démarrer
    Une nouveauté ressemble toujours au début à une solution miracle brillante et sans défaut

    • Je suis l’auteur, et je connais bien le concept de période de lune de miel
      Les premières impressions ont aussi leur place, et c’est ce que je propose ici. Le public visé n’est pas composé d’experts Elixir/Erlang, mais d’experts Ruby
    • Avant de commencer mon doctorat, j’ai travaillé professionnellement comme développeur Elixir encore plus longtemps que cela
      Elixir était déjà un joyau à l’époque et l’est toujours aujourd’hui. Il n’est pas parfait, mais il tient remarquablement bien la route
  • J’utilise Ruby depuis environ 2007
    Chaque fois que j’ai essayé de passer à la prochaine chose « cool », j’ai fini par revenir à Ruby, parce que j’aime Ruby, que c’est avec Ruby que je travaille le plus vite, et que cela suffisait
    Mais récemment, je vois beaucoup plus parler d’Elixir, et son apparence comme ses sensations me plaisent. Je vais bientôt lancer un nouveau side project et je pense que je devrais l’essayer