PureScript, ReScript y Gleam: el espectro de pureza y pragmatismo
Elm no es el único lenguaje que compila a JavaScript prometiendo tipos serios y ausencia de fallos en ejecución, y quien lo evalúa sin conocer a sus vecinos acaba atribuyéndole méritos que son del paradigma y defectos que son de una elección concreta. Esta lección sitúa a Elm dentro de un espectro poblado por al menos tres alternativas serias con filosofías incompatibles entre sí: PureScript, que lleva la abstracción funcional hasta sus últimas consecuencias con clases de tipos, polimorfismo de filas y efectos rastreados en el tipo; ReScript, que renuncia a la pureza para conseguir integración quirúrgica con el ecosistema de JavaScript y una velocidad de compilación sin rival; y Gleam, que trae el rigor de un sistema de tipos sin excepciones al mundo concurrente de la máquina virtual de Erlang y de paso emite JavaScript. Se analiza el eje real que ordena el espectro, que no es pureza contra impureza sino poder de abstracción contra tamaño del lenguaje, se examina qué gana y qué pierde cada uno en esa negociación, y se muestra por qué la posición de Elm es deliberadamente extrema y por qué esa decisión explica a la vez sus mejores propiedades y sus límites más discutidos.
Uno de los errores más comunes al evaluar Elm consiste en compararlo únicamente con lo que había antes, es decir, con JavaScript y sus dialectos anotados, y concluir de ahí que sus virtudes provienen de ser funcional y tipado. La conclusión es falsa, y basta mirar a los lados para comprobarlo: hay otros lenguajes que compilan a JavaScript, son funcionales, tienen sistemas de tipos tan buenos o mejores, y sin embargo no ofrecen las mismas propiedades ni de lejos. PureScript tiene un sistema de tipos que hace parecer modesto al de Elm y no promete ausencia de fallos en ejecución con la misma rotundidad. ReScript compila más rápido que cualquiera de los dos y se integra con el ecosistema existente de una forma que Elm jamás intentará. Gleam combina rigor total con un modelo de concurrencia que en Elm no existe siquiera como pregunta. Situar a Elm en esa vecindad obliga a un ejercicio incómodo y necesario: separar lo que es mérito del paradigma de lo que es consecuencia de un puñado de decisiones muy concretas y muy discutibles que el lenguaje tomó y sostiene contra viento y marea.
- Identificar el eje real del espectro, que negocia poder de abstracción contra tamaño del lenguaje y no pureza contra pragmatismo.
- Caracterizar las apuestas de
PureScript,ReScriptyGleampor lo que cada una decide sacrificar, no por lo que ofrece. - Explicar por qué un sistema de tipos más potente no implica automáticamente más garantías en el artefacto desplegado.
- Situar a
Elmen el extremo del espectro y derivar de esa posición tanto sus fortalezas como sus límites conocidos.
El eje no es la pureza, es el tamaño
La forma habitual de ordenar estos lenguajes los pone en una recta que va de lo pragmático a lo puro, con ReScript en un extremo y PureScript en el otro. Es una ordenación cómoda y engañosa, porque coloca a Elm en un punto intermedio que no describe nada de lo que hace especial a Elm. El eje que de verdad los separa es otro: cuánto poder de abstracción pone el lenguaje en manos de quien escribe librerías, y cuánto vocabulario tiene que aprender quien lee el código de otro. Los dos extremos de ese eje son coherentes y ninguno es un error. Un lenguaje que maximiza el poder de abstracción permite construir soluciones que capturan patrones profundos y eliminan repeticiones enteras. Un lenguaje que minimiza el vocabulario garantiza que cualquier persona del equipo pueda leer cualquier archivo del proyecto sin encontrarse construcciones que no reconoce.
PureScript está firmemente en el primer extremo. Tiene clases de tipos con todas sus consecuencias, funtores y mónadas como abstracciones nombradas y reutilizables, polimorfismo de filas real para registros extensibles, tipos de rango superior, y un sistema de efectos que aparece en las firmas. La ventaja es que una librería puede expresar exactamente su contrato algebraico y componerse con otras que hablen el mismo idioma. El precio es que ese idioma hay que aprenderlo, que dos librerías pueden resolver el mismo problema con jerarquías de abstracción incompatibles, y que leer código ajeno exige a menudo reconstruir mentalmente qué instancia se está seleccionando en cada punto.
-- Elm: la funcion dice literalmente que hace, sin abstraccion intermedia
List.map : (a -> b) -> List a -> List b
Maybe.map : (a -> b) -> Maybe a -> Maybe b
Result.map : (a -> b) -> Result e a -> Result e b
// PureScript en espiritu: una sola firma cubre las tres de arriba
// map :: forall f a b. Functor f => (a -> b) -> f a -> f b
// Gana en poder de abstraccion y pierde en obviedad local
Que Elm repita tres veces la misma forma en lugar de abstraerla no es un descuido de diseño ni una limitación que alguien vaya a corregir. Es la aplicación deliberada de un criterio: prefiero que quien lee sepa exactamente qué hace la línea que tiene delante, aunque el autor haya tenido que escribirla tres veces. La ausencia de clases de tipos en Elm es la decisión más criticada del lenguaje y también la que mejor explica por qué su código envejece de forma tan uniforme.
El coste de esa decisión es real y conviene medirlo antes de defenderla. Sin clases de tipos no se puede escribir una función que funcione sobre cualquier estructura que sepa recorrerse, ni una que combine cualquier par de valores que tengan noción de combinación, ni una librería de validación que se componga con la de serialización porque ambas hablan el mismo álgebra. En Elm cada una de esas cosas se escribe a mano para cada tipo concreto, y quien viene de un lenguaje con clases de tipos siente esa repetición como una amputación. La respuesta del diseño no es negar la amputación sino sostener que el código de aplicación repite mucho menos de lo que se teme, porque los casos donde la abstracción algebraica se rentabiliza aparecen sobre todo al escribir librerías, y Elm está optimizado para escribir aplicaciones.
PureScript
Clases de tipos, polimorfismo de filas, efectos en la firma. Máximo poder para quien escribe librerías.
ReScript
Inferencia excelente y compilación instantánea, con enlaces externos afirmados que abren el ecosistema y la frontera.
Gleam
Rigor sin excepciones sobre la máquina virtual de Erlang, con procesos aislados y supervisión.
Elm
El único sin vías de escape. Vocabulario mínimo, clausura total, ecosistema que crece despacio por diseño.
Tres apuestas y lo que cada una sacrifica
ReScript hace la apuesta contraria a la de Elm en el punto exacto donde Elm es más intransigente: la frontera. Tiene inferencia de tipos excelente heredada de la tradición de OCaml, coincidencia de patrones exhaustiva y variantes etiquetadas, pero permite declarar enlaces externos hacia cualquier valor de JavaScript con la firma que uno afirme que tiene. Esa afirmación no se verifica, exactamente igual que una aserción en TypeScript, y por esa puerta entra tanto el ecosistema entero de npm como la posibilidad de un fallo en ejecución. A cambio obtiene tres cosas notables: velocidad de compilación de otro orden de magnitud, salida legible que se parece al JavaScript que uno habría escrito, y adopción incremental archivo a archivo dentro de un proyecto existente.
Merece la pena mirar de cerca esa decisión de ReScript, porque es exactamente la misma que toma TypeScript y llega desde una tradición opuesta. Un enlace externo declara la firma que se le atribuye a una función que vive fuera del compilador, y a partir de ese punto todo el sistema razona con esa firma como si fuera verdad. Cuando lo es, el resultado es magnífico: se usa cualquier librería del mundo con tipos aprovechables y sin escribir un protocolo. Cuando no lo es, el fallo aparece en ejecución, en un punto arbitrariamente lejano de la declaración, y el sistema de tipos no puede ayudar porque él mismo fue el vehículo de la mentira. Elm rechaza esa construcción y por eso no tiene ninguna forma de usar una librería externa sin diseñar un canal de datos con serialización explícita. Ese rechazo no es purismo: es la única razón por la que puede prometer lo que promete.
-- La frontera de Elm no admite afirmaciones, solo protocolos
port enviarAlWidget : Valor -> Cmd msg
port recibirDelWidget : (Decode.Value -> msg) -> Sub msg
-- Lo que vuelve es un valor sin estructura conocida y hay que decodificarlo.
-- No existe la opcion de declarar su tipo y confiar.
Gleam es el más joven y el que trae el contexto más ajeno. Nace en la máquina virtual de Erlang, donde el modelo de concurrencia con procesos aislados y supervisión es el asunto central, y añade un sistema de tipos sin excepciones ni valores nulos. Emite también JavaScript, lo que lo convierte en candidato para interfaces, pero su diseño está pensado para sistemas distribuidos de larga vida y su vocabulario refleja esa procedencia. Su relación con la interfaz de usuario es todavía incipiente y ninguna de sus bibliotecas de vista tiene la madurez de la que Elm lleva una década puliendo.
Lo interesante de Gleam para quien viene de Elm no es su emisión de JavaScript sino la coincidencia de criterios de diseño en dos contextos que no tienen nada que ver. Es también un lenguaje deliberadamente pequeño, sin clases de tipos, con una única forma idiomática de hacer casi todo, con mensajes de error cuidados y con un compilador que trata la exhaustividad como obligación. Que dos equipos independientes lleguen a la misma familia de decisiones partiendo uno de la interfaz de usuario y otro de los sistemas distribuidos sugiere que esas decisiones no son un capricho de Elm sino una posición defendible en el espacio de diseño, y que su valor no depende del dominio sino del horizonte de mantenimiento.
Merece la pena fijar esta idea porque es contraintuitiva. PureScript puede expresar propiedades que Elm no puede ni enunciar, y sin embargo un programa medio de PureScript no ofrece necesariamente menos fallos en ejecución que uno de Elm. La razón es que la garantía no depende de la potencia del sistema sino de la hermeticidad de la frontera y de la ausencia de escapes en el lenguaje. PureScript admite funciones no seguras explícitas, interoperación directa con valores externos y llamadas que pueden lanzar excepciones del anfitrión. Cada una de esas puertas es razonable en su contexto y cada una desplaza la garantía desde la propiedad del artefacto hacia la disciplina del autor. Potencia y hermeticidad son ejes independientes, y confundirlos lleva a esperar de un lenguaje muy expresivo unas promesas que solo da un lenguaje muy cerrado.
| Lenguaje | Sacrifica | Obtiene |
|---|---|---|
Elm |
Abstracción, ecosistema, escapes | Ausencia de fallos en ejecución y uniformidad total |
PureScript |
Tamaño del lenguaje y accesibilidad | Poder algebraico y efectos en el tipo |
ReScript |
Hermeticidad de la frontera | Interoperación real y compilación instantánea |
Gleam |
Madurez en interfaz de usuario | Concurrencia con procesos y rigor sin excepciones |
La tabla se lee mejor con una advertencia: la columna de lo que se sacrifica no describe defectos sino precios pagados a conciencia. Ninguno de los cuatro equipos de diseño ignoraba lo que estaba perdiendo, y en los cuatro casos existe una justificación escrita y coherente de por qué esa pérdida compensa. Leer esas justificaciones antes de opinar cambia bastante la conversación, porque casi todas las críticas que circulan sobre cada uno de estos lenguajes consisten en señalar exactamente el precio que su diseño anunció desde el primer día.
Conviene retirar de la conversación un prejuicio que aparece en cuanto se nombra a estos lenguajes juntos: el de que se trata de curiosidades académicas frente a opciones industriales. Los cuatro tienen productos en producción con usuarios reales, empresas que facturan con ellos y bases de código de años. Lo que los distingue no es la seriedad sino la escala de su adopción, y esa escala tiene consecuencias prácticas que sí hay que sopesar —cantidad de material de aprendizaje, número de personas con experiencia, frecuencia con que alguien ya se ha topado con tu problema exacto—. Confundir esas consecuencias con inmadurez técnica lleva a descartar por la razón equivocada, y a no evaluar las razones correctas.
Por qué la posición de Elm es extrema y coherente
-- Las cuatro ausencias que definen la clausura de Elm
-- 1. No hay conversion forzada entre tipos
-- 2. No hay declaracion de firma para valores externos
-- 3. No hay excepciones que capturar ni fallos que ignorar
-- 4. No hay efecto que una firma no anuncie
-- Todo valor externo entra por aqui, y esta es la unica puerta
decodificar : Decode.Value -> Result Decode.Error Configuracion
Puesto el espectro sobre la mesa, la posición de Elm deja de parecer intermedia y se revela como la más extrema de las cuatro en un eje muy concreto: es el único que no ofrece ninguna vía de escape. No hay función insegura, no hay enlace externo con firma afirmada, no hay excepción que atrape un fallo del anfitrión, no hay conversión forzada de tipos. Todo lo que entra pasa por un decoder o por un port, y ambos producen valores que el sistema de tipos ya conoce. Esa clausura es la fuente única de su promesa práctica, y explica también por qué no puede tener las otras propiedades: un lenguaje sin escapes no puede envolver una librería arbitraria sin escribir un protocolo, y por eso su ecosistema crece más despacio.
Conviene precisar en qué consiste esa clausura, porque se enuncia a menudo como eslogan. Significa cuatro ausencias concretas, y las cuatro son verificables leyendo la referencia del lenguaje: no hay una función que convierta un valor de un tipo en otro sin comprobación, no hay forma de declarar la firma de algo que vive fuera y usarlo directamente, no hay excepciones que se puedan capturar y por tanto tampoco fallos que se puedan ignorar, y no hay ningún mecanismo por el que una dependencia produzca un efecto que su firma no anuncie. Los otros tres lenguajes del espectro tienen al menos una de esas cuatro puertas abiertas, y cada uno tiene buenas razones para haberla abierto. La diferencia relevante no es de grado sino de categoría: con una sola puerta abierta, la promesa deja de ser una propiedad del artefacto y vuelve a ser una expectativa sobre el comportamiento de quien escribe.
La segunda decisión extrema, y la que más discusiones genera, es el tamaño congelado. Elm lleva años sin añadir construcciones al lenguaje, y esa quietud se lee de dos formas incompatibles. Para quien busca poder expresivo es estancamiento y motivo suficiente para irse a PureScript. Para quien mantiene una base de código con varios años y varias generaciones de personas, es la propiedad más valiosa que puede tener una herramienta: el código escrito hace cinco años se lee hoy sin traducción, no hay dos formas idiomáticas de hacer lo mismo, y nadie tiene que aprender lo que se puso de moda el trimestre pasado. Cuál de las dos lecturas es la correcta no se decide en abstracto; se decide sabiendo si el proyecto vivirá dos años o diez y quién lo mantendrá.
Hay un tercer rasgo que suele pasar desapercibido y que separa a Elm incluso de sus vecinos más cercanos: la arquitectura viene incluida. PureScript no impone una forma de estructurar una aplicación de interfaz, y por eso su ecosistema tiene varias, cada una con su propio modelo de componentes, su propia relación con los efectos y sus propias convenciones. ReScript tampoco, y lo habitual es usarlo con la arquitectura del ecosistema de React. Gleam está todavía decidiéndolo. Elm trae una sola, que además no es una librería sino la firma del punto de entrada, de modo que todos los proyectos del mundo escritos en el lenguaje comparten esqueleto. Esa uniformidad es la razón por la que abrir un proyecto ajeno en Elm produce una sensación de familiaridad que ninguno de los otros tres consigue.
Un vicio recurrente al elegir entre estas alternativas consiste en evaluarlas por lo más sofisticado que permiten hacer, cuando la experiencia diaria de un equipo la determina lo más común que se hace. El techo de PureScript es altísimo y prácticamente ningún proyecto de aplicación lo roza; la mayoría del código que se escribe en él consiste en transformar datos, dibujar listas y tratar respuestas del servidor, exactamente igual que en Elm. La diferencia práctica no aparece entonces en la potencia disponible sino en cuántas formas distintas de hacer lo mismo conviven en el proyecto y cuántas de ellas tiene que reconocer quien llega. Evaluar por el techo elige el lenguaje para el diez por ciento del trabajo; evaluar por la mediana lo elige para el noventa restante.
flowchart LR A[Poder de abstraccion alto] --> P[PureScript] B[Interoperacion y velocidad] --> R[ReScript] C[Concurrencia con procesos] --> G[Gleam] D[Clausura total sin escapes] --> E[Elm] P --> X[Vocabulario grande que hay que aprender] R --> Y[Frontera afirmada y no verificada] G --> Z[Interfaz de usuario aun incipiente] E --> W[Ecosistema pequeno y sin escapes] style E fill:#a6e3a1,color:#11111b style P fill:#cba6f7,color:#11111b style R fill:#89b4fa,color:#11111b style G fill:#f9e2af,color:#11111b
Hay una tentación difícil de resistir al comparar lenguajes, que consiste en buscar un orden total: uno mejor que otro, y ese mejor que el siguiente. El espectro que acabamos de recorrer demuestra que ese orden no existe, y no por relativismo cortés sino por una razón estructural. Cada una de estas tecnologías optimiza una función objetivo distinta y todas pagan por ello en la misma moneda, que es capacidad renunciada. PureScript maximiza lo que se puede construir y acepta que leer código ajeno exija reconstruir un contexto de abstracciones que no está en la línea que tienes delante. ReScript maximiza la integración con lo que ya existe y acepta que la frontera sea una promesa. Gleam maximiza el rigor en un dominio donde los fallos son de otro tipo y acepta llegar tarde a la interfaz de usuario. Elm maximiza lo que se puede garantizar sin leer y acepta que haya cosas que simplemente no se pueden expresar y librerías que simplemente no existen. La lección que trasciende a los cuatro es que la pregunta útil no es cuál es mejor sino cuál es la variable que tu proyecto no puede permitirse perder, porque esa variable determina en qué punto del espectro tienes que aterrizar. Un equipo pequeño de expertos que construye una librería de dominio complejo necesita poder de abstracción y puede permitirse el coste de lectura. Un producto de larga vida mantenido por gente que rota necesita uniformidad y puede permitirse escribir tres funciones donde cabría una. Confundir esas dos situaciones y elegir por prestigio técnico en lugar de por restricción real es el error más caro que se comete al elegir tecnología, y se comete constantemente porque la potencia se demuestra en una charla de veinte minutos mientras la uniformidad solo se demuestra al cabo de tres años.
- Escribe la misma transformación sobre una lista, un
Maybey unResultenElm, y razona qué se gana y qué se pierde al no poder abstraerla en una sola firma. - Busca en
ReScriptcómo se declara un enlace externo y compáralo puerta por puerta con unportdeElm; enumera qué se verifica en cada caso. - Enumera tres construcciones de
PureScriptqueElmno tiene y decide, para cada una, si tu proyecto las necesitaría de verdad o solo las disfrutaría. - Toma una librería de
npmque tu proyecto usa y describe el coste de integrarla en cada uno de los cuatro lenguajes. - Escribe cuál es la variable que tu proyecto no puede permitirse perder y comprueba en qué punto del espectro aterriza esa restricción.
- Comprueba cuál de las cuatro ausencias de la clausura de
Elmtiene abierta cada uno de los otros tres lenguajes y qué gana con ello. - Evalúa tu proyecto por su mediana en lugar de por su techo: cuenta qué proporción del código transforma datos y dibuja listas.
- Argumenta que el tamaño congelado de
Elmes un defecto; después argumenta que es su mejor propiedad, usando el mismo proyecto como ejemplo.