2 puntos por GN⁺ 2023-11-11 | 1 comentarios | Compartir por WhatsApp
  • Un desarrollador backend de Ruby se unió a una empresa que usa Elixir como lenguaje principal y, gracias a una sintaxis influenciada por Ruby, pudo adaptarse relativamente rápido incluso sin experiencia previa con Elixir/Erlang
  • Elixir ofrece, como Ruby, una sintaxis divertida y afinidad con los DSL, pero con un modelo funcional inmutable sin clases, instancias ni herencia, lo que reduce la carga del manejo de estado
  • Mientras que los DSL de Ruby dependen de definir dinámicamente objetos y métodos en tiempo de ejecución, los DSL de Elixir usan macros en tiempo de compilación para generar código de runtime, haciendo que los errores aparezcan antes
  • La sintaxis de pipeline, la expresión with y las tuplas Result con forma de {:ok, result} son útiles para separar el flujo normal del manejo de errores recuperables
  • Frente al enfoque de Ruby, que suele apoyarse en excepciones, nil/false y estados de error internos en objetos, las devoluciones explícitas ok/error de Elixir resultan más satisfactorias, y después de 3 meses de uso sigue siendo agradable escribirlo

Cómo alguien con experiencia en Ruby se adaptó a Elixir

  • En su nuevo trabajo usan Elixir como lenguaje principal del backend, y antes de empezar no tenía experiencia escribiendo código en Elixir ni en Erlang
  • Erlang no le resultó del todo ajeno porque había tocado un poco de Prolog, y Elixir pudo aprenderlo rápido gracias a una sintaxis fuertemente influida por Ruby
  • Aunque todavía no domina las mejores prácticas, la arquitectura ni conceptos más de bajo nivel de Erlang, eso no le impidió entrar a Elixir
  • Elixir se percibe como un lenguaje que mantiene la diversión de programar que ofrece Ruby, pero reduce las trampas relacionadas con el estado que son comunes en los lenguajes OOP
    • No hay clases, instancias ni herencia
    • Se basa en la inmutabilidad y un estilo funcional
    • No está atado a un sistema de tipos estático
  • El código con estado mutable es mucho más difícil de mantener, y no siempre está claro si el estado es local a una función, de una instancia o de una clase
  • En Ruby todo es un objeto, así que cada objeto puede tener estado, y al intentar crear DSL intuitivos es fácil mezclar estado a nivel de función, instancia y clase
  • La eigenclass de Ruby es algo así como la clase de una clase, y una instancia puede modificar su eigenclass y afectar también el estado de otras instancias

La diferencia entre los DSL y las macros en tiempo de compilación

  • Elixir, igual que Ruby, también soporta DSL y su sintaxis también resulta familiar, pero la diferencia clave está en cuándo se crean esos DSL
    • Los DSL de Elixir son macros en tiempo de compilación y generan código que se ejecuta en runtime
    • Los DSL de Ruby dependen de modificar el runtime para definir dinámicamente objetos y métodos
  • Si se usan mal las macros de Elixir, los errores aparecen en la librería, y si el código generado por la macro está mal, el compilador lanza un error
    • Como esos errores aparecen en tiempo de compilación, las pruebas pueden enfocarse más en la lógica real de la aplicación

El flujo que crean el pipeline y with

  • El operador de pipeline |> de Elixir es útil, pero los operadores de threading de Clojure también tienen ventajas
    • -> en Clojure inserta el resultado de la expresión anterior como primer argumento de la siguiente función, igual que |> en Elixir
    • ->> en Clojure inserta el resultado anterior como último argumento de la siguiente función
    • Aunque la diferencia parezca pequeña, es útil al escribir código de interoperabilidad sin recurrir a funciones anónimas
  • La expresión with de Elixir recuerda al let de Haskell o Lisp, pero funciona como si llevara un otherwise integrado
    • Permite dividir funciones complejas entre la ruta normal arriba y el manejo de errores abajo
    • Si no se puede hacer pattern matching con {:ok, result}, puede tratarse como un error recuperable, así que encaja bien con las tuplas Result

Las ventajas de devolver errores de forma explícita

  • En Ruby es común usar excepciones para el flujo de control y, en lugar de tipos de error explícitos, muchas veces se depende de un resultado exitoso, nil, false o un estado de error dentro del objeto
    • Por ejemplo, model.update(params) devuelve false si falla y guarda la razón del fallo en model.errors
    • Esto vuelve a llevar al problema del estado mutable
  • Un enfoque como los mónadas Result / Either, donde ok/error o left/right se expresan explícitamente en el valor de retorno, se siente más satisfactorio que el flujo basado en excepciones típico de Ruby
  • Incluso después de 3 meses usando Elixir, sigue siendo un lenguaje que conserva esa sensación de que “programar es divertido” que Ruby mostraba

1 comentarios

 
GN⁺ 2023-11-11
Opiniones de Hacker News
  • En las últimas semanas he estado viendo videos de Elixir en YouTube casi todos los días.
    Parece que hace poco hubo una conferencia de Elixir y, después de ver algunos, YouTube no deja de recomendarme contenido sobre Elixir.
    Me gustan las ideas de Erlang, pero cuando antes trabajé con cosas como el servidor de chat ejabberd, me pareció demasiado peculiar.
    Cada vez que veo videos de Joe Armstrong pienso que fue un genio subestimado, y los procesos aislados y el paso de mensajes, es decir, el modelo de actores, se ven como un buen candidato para el futuro de la programación distribuida.
    También me llamó la atención que en el video del nuevo lenguaje Misty de Crockford https://www.youtube.com/watch?v=R2idkNdKqpQ las preguntas se fueron hacia Elixir/Erlang.
    Dicho eso, al ver la “magia” de LiveView y LiveBook todavía no estoy convencido al 100%. Las cosas que se ven demasiado buenas suelen tener una trampa, y términos desconocidos como GenServer y el conocimiento específico de la plataforma se sienten como entrar por un portal de un solo sentido; no sé si sea conocimiento transferible a otros lugares.
    En un contexto en el que Go también tiene una historia bastante buena de concurrencia, un lenguaje y una plataforma con una comunidad pequeña implican muchos riesgos en contratación, bibliotecas y material. Aun así, lo que ha logrado la comunidad de Elixir es realmente impresionante, y también tengo ganas de ver cómo resultará la implementación del sistema de tipos.

    • Creo que te estás preocupando un poco de más por la concurrencia. Basta con entender qué es un proceso y cómo funciona send.
      GenServer es una generalización natural de un patrón que terminarías escribiendo tú mismo incontables veces, y el conocimiento sobre actores sí se puede trasladar a otros lugares.
      En Go también hay bibliotecas que implementan abstracciones de actores, y el buzón de un proceso puede verse como una cola de mensajes, igual que los canales de Go. La diferencia está más en cómo funcionan el intérprete y la cesión de tiempo del procesador.
      En cambio, sí es cierto que LiveView parece magia. En la práctica tiene muchas piezas móviles encajando entre sí, y es el resultado de trabajo acumulado durante los últimos 10 años en varias comunidades; aunque hay muchas abstracciones, la idea en sí ya está madura.
      Cosas como Riak se adelantaron a su época, y casi tenían la idea de que se podían construir sistemas distribuidos robustos de la misma forma en que se construye un GenServer.
    • Hay una necesidad real de empujar Elixir más allá de su comunidad inicial, y siento que eso ya está ocurriendo en cierta medida.
      Tanto Chris como José se están moviendo mucho para aumentar la exposición fuera del círculo inicial de Elixir, y la gente dentro de la comunidad también está haciendo lo mismo a su propio nivel.
      Contratar no es tan complicado como uno pensaría. Hay bastante gente que quiere trabajar con este lenguaje, y los buenos desarrolladores se ponen al día bastante rápido.
      Aun así, hay que asumir y manejar el hecho de que es un lenguaje de nicho. Eso es un riesgo para el lenguaje en sí, pero a corto plazo también puede recompensar el dominio de una tecnología de nicho.
    • Yo lo siento más bien al revés. Elixir se vuelve menos mágico una vez que lo aprendes.
      En la mayoría de los casos puedes ver qué hace algo, entenderlo y leer el código. Con JavaScript muchas veces no pasa eso.
      Es cierto que contratar y encontrar tutoriales es difícil. Por ejemplo, encontrar a alguien que ya haya hecho en Elixir lo que yo quiero hacer es mucho más difícil que en JavaScript.
      Las bibliotecas no han sido un gran problema; hay bastantes. Al trabajar con APIs, a veces existen bibliotecas cliente para JavaScript o Python pero no para Elixir, pero normalmente no es tan difícil usar una API solo con un cliente HTTP.
    • Pasé de Ruby a Elixir, y luego de vuelta a Node.js.
      Ahora uso Node.js no porque quiera, sino porque tengo que hacerlo. Después de usarlo, pienso que jamás empezaría un proyecto nuevo con Node.js.
      Las herramientas de control de concurrencia, escalabilidad, confiabilidad y observabilidad de Node.js están muy por detrás del ecosistema BEAM.
      Creo que cuando a la gente le cuesta creer que Elixir sea tan bueno, a veces es porque está demasiado acostumbrada a plataformas que no fueron diseñadas para la resiliencia.
    • No entiendo bien cuál sería la magia de LiveView y LiveBook.
      LiveBook es bastante fácil de modelar mentalmente de forma correcta, salvo por los detalles exactos del cálculo de diferencias o la compresión de datos. Si hay apuro, incluso puedes mirar los mensajes de WebSocket y entender bastante bien qué está pasando.
      LiveView es sorprendentemente intuitivo.
  • Un gran atractivo de Elixir/Erlang es que, en el contexto de un servidor web, permite ejecutar trabajos en segundo plano con mucha facilidad, sin tener que preocuparse de que una E/S bloqueante detenga todo el servidor.
    En mi trabajo anterior teníamos que hacer muchas solicitudes HTTP dentro de un manejador de webhooks, y cuando ocurrían suficientes al mismo tiempo, todos los procesos del sistema operativo se ocupaban y todo el sitio se caía.
    En esos momentos pensaba con fuerza que, si hubiera sido Elixir, habría bastado con lanzar una Task y seguir adelante.

    • Hace poco cambiamos un nuevo servicio de Rust a Elixir, y este punto fue una razón bastante importante.
      La idea de compartir objetos entre procesos asíncronos de Tokio se sentía difícil frente a una funcionalidad de primera clase de Elixir. No digo que sea imposible ni intrínsecamente difícil, pero parecía más difícil hacerlo correctamente.
    • No quiero tanto que los trabajos en segundo plano se vuelvan fáciles de esa manera.
      Quiero poner los trabajos en segundo plano sobre una capacidad de cómputo distinta a la de las solicitudes HTTP. Tienen patrones de uso de recursos diferentes, y quiero tener estado o una cola por delante para dejar claros los reintentos, el manejo de errores y la contrapresión.
      Claro que sería genial no tener esa complejidad y que el mismo framework se encargara de todo, pero creo que es difícil llegar hasta ahí solo con un lenguaje de programación o un runtime. El monitoreo y los procedimientos operativos también tienen que formar parte de la solución.
  • La charla The Soul of Erlang and Elixir de Sasa Juric muestra muy bien lo poderoso que puede ser este lenguaje.
    https://www.youtube.com/watch?v=JvBT4XBdoUE&t=4

    • Vi esta charla a principios de esta semana porque tenía curiosidad por Elixir.
      La recomiendo mucho para quienes han oído frases como “déjalo fallar” o “BEAM hace bien la concurrencia”, pero todavía no terminan de captar la idea. Es una charla realmente excelente.
  • Desde 1991 programo todos los días en Erlang, y pensaba seguir haciéndolo.
    En ese tiempo también probé Haskell, Rust, Elixir, etc. Los tipos fuertes son muy valiosos, pero al final todo se reduce a dónde te diviertes más.
    Erlang es el lenguaje de programación que más disfrute me da, y planeo seguir hackeando en Erlang al menos hasta que me jubile dentro de unos años.

    • Me da curiosidad saber qué tipo de proyectos fueron los más divertidos para trabajar.
  • Conocí Elixir hace poco más de un año, y el proceso de aprenderlo e incorporarlo en varios componentes fue bastante entretenido.
    La mayor satisfacción viene de que BEAM/OTP trae muchas cosas de base por sí solo.
    Alguien del equipo con el que trabajaba dijo una vez: “BEAM/OTP es como k8s, pero sin las partes complicadas”.

    • Así se lo explico al equipo cuando otras explicaciones no funcionan.
      Aprendí Elixir durante varios años, pero una migración a OpenShift, es decir, k8s, que diseñé recientemente diluyó en cierta medida esa ventaja.
      Si no existiera k8s, habría sido una propuesta mucho más atractiva. En cambio, mantuvimos el stack de desarrollo existente y logramos con conceptos de k8s lo que habríamos hecho con OTP. Claro que los trade-offs fueron distintos.
  • “Elixir te da un lenguaje divertido como Ruby, pero te quita las minas antipersonales relacionadas con el estado de los lenguajes orientados a objetos. No hay clases, ni instancias, ni herencia; es inmutable y funcional, y no te ata a un sistema de tipos estático”.
    Quiero eso, pero también quisiera que tuviera tipos. En una base de código grande, estoy convencido de que los tipos fuertes son lo correcto, porque exponen mucha magia y hacen que sea más fácil razonar.

    • En Elixir este problema es menor que en otros lenguajes dinámicos como Ruby o Python, o que en lenguajes estáticos con mutabilidad.
      Gracias al pattern matching/unificación de las funciones, se puede saber bastante bien qué forma tienen los datos cuando van y vienen. Aun así, si te preocupa, está Dialyzer.
      Dialyzer es una herramienta de análisis estático para Erlang/Elixir. Viene incluida en la distribución estándar de Erlang, y su nombre significa “DIscrepancy AnaLYZer for ERlang programs”.
      Detecta discrepancias como errores de tipo, código muerto y pruebas innecesarias en un solo módulo de Erlang o en un conjunto de aplicaciones. Infiere tipos a partir de programas correctos, y las anotaciones de tipo no son obligatorias, aunque si existen ofrecen mejores advertencias.
      https://fly.io/phoenix-files/adding-dialyzer-without-the-pai...
    • Los tipos vienen en camino. Ojalá no estén demasiado lejos.
      https://elixir-lang.org/blog/2023/06/22/type-system-updates-...
    • Yo aquí estoy en la postura contraria. Realmente me gustaría que los GenServer de Elixir tuvieran herencia.
      GenServer se parece más a un objeto e interactúa mediante mensajes, no es simplemente un struct con funciones asociadas.
      Me gustaría poder tener una plantilla base para los GenServer del proyecto, con registro común, configuración de memoria, logging, etc., y especializarla para tres variantes de tareas específicas.
      Elixir tapa este problema con generación de código mediante macros, pero no es gran cosa. Mis GenServer están llenos de repeticiones triviales.
    • En ese caso, quizá te guste Gleam. Parece tener una sensación parecida.
  • Creo que decir que “en Ruby es común usar excepciones para el flujo de control” simplemente es falso.
    El ejemplo después de ese párrafo es el método update de Rails, pero la forma usada en los ejemplos y generadores estándar de Rails es la versión de update que no usa excepciones.

    • El ejemplo muestra una forma sencilla de limitar la complejidad, especialmente para quienes están aprendiendo.
      Cuando una biblioteca o aplicación de cierto tamaño se vuelve lo suficientemente compleja, pasa a clases de error descriptivas. El manejo de errores basado en estado, como en ActiveRecord, es doloroso.
      Como Ruby ahora tiene cierto grado de pattern matching, creo que puede reemplazar las excepciones para flujo de control. En vez de lanzar una excepción, se puede devolver la propia clase y hacer matching sobre ella.
    • Como alguien que usa Ruby desde 2004, no me parece en absoluto falso.
      Muchas gem usan excepciones para flujo de control, y también era común en apps y bibliotecas que escribí o mantuve.
    • Ruby y Rails no son lo mismo.
      Rails tiene muchos métodos bang! que lanzan excepciones en caso de error. Pero, en general, en Ruby, igual que en Python, realmente es común usar excepciones para flujo de control.
  • Vengo de F# y apenas he probado un poco Elixir, pero lamentablemente el lenguaje me resulta algo confuso.
    Por ejemplo, las firmas de las funciones solo muestran el nombre y la cantidad de parámetros, no los tipos, así que en bibliotecas grandes es bastante difícil entender mentalmente qué va en dónde.
    A menudo uno se da cuenta de que pasó un parámetro incorrecto recién después de ejecutar y depurar el código. En lenguajes con tipado estricto como F#, estos problemas se detectan durante la codificación, lo que reduce mucho el tiempo y el esfuerzo.
    Por eso me pregunto cuál es el principal atractivo de Elixir fuera del entorno Erlang.

    • El tipado estático de Elixir está en desarrollo activo.
      https://elixir-lang.org/blog/2023/09/20/strong-arrows-gradua...
      Hoy por hoy sí es un lenguaje dinámico, así que no se obtiene mucha información de tipos del lenguaje en sí. Todavía no es un lenguaje de tipado estático.
    • Elixir en sí también tiene mucho para gustar. Como dice el artículo, la sintaxis inspirada en Ruby es agradable a la vista y fácil de aprender.
      Pero lo que realmente le da ventaja a Elixir frente a otros lenguajes que he usado es BEAM/OTP.
      Por experiencia, muchas funcionalidades que en otros lenguajes tendrías que traer como bibliotecas vienen incluidas de base. Esas bibliotecas suelen aumentar mucho la complejidad y a veces también tienen una curva de aprendizaje pronunciada. Las funcionalidades relacionadas con asincronía son un buen ejemplo.
      Creo que, después de usar Elixir un tiempo y familiarizarte con estas herramientas y abstracciones integradas, entiendes la potencia de todo el ecosistema.
    • Si hablamos solo del lenguaje, no lo llamaría una mejora respecto de F#. Es subjetivo, claro.
      El principal atractivo probablemente sea que corre sobre BEAM con una sintaxis con aire a Ruby. Creo que BEAM es una ventaja bastante grande.
      No se compara bien con los tipos de F#, pero se pueden usar typespecs: https://hexdocs.pm/elixir/1.15.7/typespecs.html
    • He usado bastante .NET y F# solo de vez en cuando, pero Elixir tiene varios atractivos.
      Por ejemplo, hay un flujo en el que el machine learning se integra de forma nativa con Axon, Nx y BumbleBee, e incluso permite compilar para GPU: https://www.youtube.com/watch?v=HK38-HIK6NA
      La comunidad realmente está impulsando fuerte esa área.
      También es atractivo cómo funciona LiveView. Aunque .NET tiene Blazor.
      Elixir puede usarse en distintos contextos, como Nerves para embebidos o scripting. Entiendo que F# también puede usarse para scripting.
      En general, BEAM y sus estructuras relacionadas son sus puntos fuertes, y el gestor de paquetes Hex también es realmente bueno. Claro que .NET también tiene buenas herramientas.
    • La falta de pistas de tipo en los parámetros de funciones es lo que más golpea la productividad en Elixir.
      Puedes arreglártelas con typespecs, pattern matching y guards, pero es bastante engorroso y, al menos en el plugin de JetBrains impulsado por la comunidad, no es muy amigable para IDE/IntelliSense.
  • Estaría bueno escribir sobre esto no al empezar, sino después de usarlo 2 años.
    Lo nuevo al principio siempre parece una solución universal brillante y sin defectos.

    • Soy el autor del texto y conozco el concepto de período de luna de miel.
      Las primeras impresiones también tienen su lugar, y eso es lo que estoy ofreciendo. El público no son expertos en Elixir/Erlang, sino expertos en Ruby.
    • Trabajé profesionalmente como desarrollador de Elixir durante más tiempo que eso antes de empezar el doctorado.
      Elixir era una joya entonces y lo sigue siendo ahora. No es perfecto, pero aguanta sorprendentemente bien.
  • Uso Ruby desde alrededor de 2007.
    Cada vez que intenté pasarme a la siguiente cosa “genial”, terminé volviendo a Ruby, porque me gusta Ruby, trabajo más rápido con Ruby y eso era todo.
    Pero últimamente veo muchas más conversaciones sobre Elixir, y me gusta su aspecto y sensación. Creo que pronto voy a empezar un nuevo proyecto paralelo y probarlo.