wandres.dev
EL LEGADO DE ELM · lo que sembró

Las ideas que se quedaron: el patrimonio común del frontend

Elm no ganó la guerra de la adopción y no la va a ganar. Su cuota de uso es marginal, su ritmo de publicaciones se detuvo casi por completo y la discusión pública sobre su gobernanza ahuyentó a parte de quienes lo habían defendido. Y sin embargo, un programador que hoy escriba interfaces en React, en SwiftUI, en Compose o en Svelte trabaja rodeado de decisiones que hace una década eran tesis minoritarias de esa comunidad y hoy son valores por defecto que nadie discute ni atribuye. Esta lección hace el inventario final de ese patrimonio, distingue lo que se quedó de lo que se quedó por el camino y se detiene en la diferencia entre ambas listas, que resulta ser sistemática: sobrevivió todo lo que podía adoptarse como patrón y no sobrevivió casi nada de lo que exigía renunciar a una escotilla de escape. La consecuencia es que la industria heredó las formas sin los mecanismos que las hacían ciertas, y que cada equipo vuelve a pagar a mano, en linters y revisiones, lo que en el original venía impuesto. Se cierra con un método concreto para auditar la propia base de código preguntando, garantía por garantía, quién la sostiene ahora.

⏱ 18 min

Hay lenguajes que ganan por difusión y lenguajes que ganan por infiltración, y confundir las dos cosas lleva a diagnósticos absurdos sobre el éxito o el fracaso. Smalltalk nunca fue mayoritario y su vocabulario está dentro de todo lo que programamos; Lisp lleva sesenta años siendo minoritario y las funciones de primera clase, la recolección de basura y las macros están en todas partes. Elm pertenece a esa familia, y su caso es aún más nítido porque el trasvase ocurrió en poco más de una década y quedó documentado en blogs, charlas y notas de versión que cualquiera puede consultar. En 2013 defender que una interfaz debía ser una función pura del estado, que los estados imposibles debían ser imposibles de escribir y que un compilador debía disculparse por interrumpirte eran posiciones excéntricas que había que argumentar en cada conversación. Hoy son el contenido de la documentación oficial de los frameworks más usados del mundo, escritas como si siempre hubieran sido obvias. Esta lección hace el recuento de ese patrimonio con dos cautelas. La primera es no atribuir a Elm la paternidad de ideas que venían de la familia de lenguajes de la que él mismo bebía, porque los tipos suma y la ausencia de nulo llevaban décadas en circulación. La segunda, más importante, es no dejarse llevar por la celebración y mirar de frente lo que no se transmitió, porque en esa segunda lista está la parte útil de la herencia para quien tiene que decidir hoy cómo se construye un sistema.

🎯 Al terminar esta lección sabrás
  • Inventariar las ideas que pasaron a ser valores por defecto del oficio y localizar su forma actual en los entornos de uso mayoritario.
  • Enunciar el criterio que separa lo heredado de lo descartado y comprobar que es sistemático y no accidental.
  • Explicar por qué una garantía degradada a convención vuelve a producir la misma familia de fallos que evitaba.
  • Auditar una base de código real preguntando, para cada garantía, qué mecanismo la sostiene y a qué coste.

Lo que ya nadie discute

La primera idea que se quedó es la vista como función pura del estado. React la popularizó, SwiftUI la impone, Compose la impone, Svelte la asume y hasta las bibliotecas de reactividad de grano fino la respetan aunque cambien el mecanismo de propagación. La segunda es el flujo unidireccional con una única fuente de verdad, que sobrevive incluso allí donde el estado no está centralizado, porque la regla operativa que se generalizó no es hay un solo estado sino cada dato tiene un dueño y solo su dueño lo modifica. La tercera es el evento como dato: acciones serializables, intenciones, mensajes, llámense como se llamen, en lugar de llamadas a métodos, y con ellas toda la familia de herramientas que solo son posibles si el evento es un valor, desde los depuradores con historial hasta la reproducción de sesiones a partir de un registro.

La cuarta y la quinta vienen del sistema de tipos y son, con diferencia, las que más han cambiado el trabajo cotidiano. Modelar con tipos suma para que los estados imposibles no se puedan escribir dejó de ser una excentricidad académica cuando las uniones discriminadas de TypeScript, las interfaces selladas de Kotlin, las enumeraciones con valores asociados de Swift y las de Rust pusieron la herramienta al alcance de todo el mundo; y la eliminación del nulo mediante un tipo opcional explícito es hoy la posición mayoritaria en todos los lenguajes que se diseñan de nuevo. Ninguna de las dos nació en Elm, y decirlo importa, pero Elm fue el vehículo que las trajo al frontend y la comunidad donde se aprendió a explicarlas a programadores que no venían de la teoría de lenguajes.

// Antes: cuatro campos que permiten dieciseis combinaciones,
// de las cuales solo tres tienen sentido
type Pantalla = {
  cargando: boolean
  error: string | null
  datos: Usuario[] | null
  vacio: boolean
}

// Despues: tres estados y ninguno mas
type Pantalla =
  | { estado: 'cargando' }
  | { estado: 'error'; mensaje: string }
  | { estado: 'listo'; datos: Usuario[] }

La sexta idea es el error como valor en lugar del error como interrupción. Devolver un tipo que representa el éxito o el fallo, y obligar a quien lo recibe a decidir qué hace con cada rama, era en 2013 una práctica de comunidades minoritarias frente a la excepción como mecanismo universal; hoy es la posición del lenguaje de sistemas más influyente de la década, es lo que hacen las bibliotecas serias de acceso a red en todos los ecosistemas y es lo que la mayoría de las guías de estilo recomienda para los fallos previsibles. La séptima, y la más incompleta de todas, es el efecto como descripción: sobrevivió allí donde había un runtime dispuesto a ejecutar la descripción, como en Swift con el linaje de Point-Free, y se quedó a medias donde no lo había, que es la historia que ya conocemos de los middleware y de los canales de sucesos.

-- Dos tipos que hoy tienen equivalente exacto en Swift, Kotlin, Rust y TypeScript
type Maybe a
    = Nothing
    | Just a


type Result error valor
    = Err error
    | Ok valor


-- La consecuencia practica: el fallo esta en la firma, no en la documentacion
buscarUsuario : Id -> Result String Usuario
🖼️

Vista pura

Describir lo que debe verse en lugar de ordenar cambios sobre lo que ya se ve. Universal en los cuatro entornos mayoritarios.

🌳

Fuente única de verdad

Cada dato con un dueño y una sola vía de escritura. Sobrevive incluso donde el estado se reparte.

✉️

Eventos como datos

Mensajes serializables en vez de llamadas. Habilitan historial, reproducción y diagnóstico de fallos en producción.

🚫

Estados imposibles imposibles

Tipos suma con comprobación de exhaustividad, disponibles hoy en todos los lenguajes de aplicación relevantes.

🕳️

Sin nulo implícito

El valor ausente se declara en el tipo y el compilador exige tratarlo. Ya es la posición por defecto de los lenguajes nuevos.

💬

Errores para humanos

El diagnóstico como parte del producto. Ningún compilador nuevo se atreve hoy a publicar mensajes crípticos.

Lo que se quedó en el camino

La lista de lo que no se transmitió es más corta y mucho más reveladora, porque todos sus elementos comparten un rasgo. La pureza obligatoria no viajó: en todos los entornos herederos es posible escribir un efecto en medio de una función que dice ser una transición, y solo la disciplina lo impide. La ausencia de escotillas de escape no viajó: TypeScript tiene una anotación que apaga el verificador, Kotlin tiene la doble exclamación, Swift tiene el desenvuelto forzoso, y en todos los casos existen porque sin ellas la migración de código antiguo habría sido imposible. La interoperación exclusivamente por puertos no viajó: nadie aceptó que hablar con el anfitrión requiriera declarar antes cada canal. Y el versionado mayor decidido por el compilador comparando interfaces públicas tampoco viajó, pese a ser la idea más obviamente correcta de todo el conjunto.

// Las tres escotillas mas usadas del ecosistema, y lo que cuesta cada una
const usuario = datos as Usuario          // apago la comprobacion aqui
// @ts-expect-error                        // apago la comprobacion en esta linea
const nombre = respuesta!.nombre          // afirmo que no es nulo sin probarlo

Ninguna de las tres es un error de diseño de sus lenguajes; las tres son el precio explícito que se pagó para que millones de líneas existentes pudieran migrar de forma gradual. Pero conviene ver lo que ocurre con ellas en la práctica: se introducen para desbloquear una migración, se quedan, se copian por imitación y acaban apareciendo en código nuevo escrito años después, cuando ya no queda nada que migrar. La escotilla temporal es la forma más común de deuda permanente.

⚠️
El criterio es sistemático, no casual

Sobrevivió todo lo que se podía adoptar como patrón, es decir, todo aquello que un equipo puede empezar a hacer el lunes sin renunciar a nada. No sobrevivió nada de lo que exigía cerrar una puerta. Esto no es una acusación de cobardía: cerrar puertas es exactamente lo que impide adoptar una tecnología de forma gradual en una base de código existente, y la adopción gradual es una condición de supervivencia para cualquier herramienta que aspire a usarse en la industria. Elm eligió la coherencia y pagó con la adopción; sus herederos eligieron la adopción y pagaron con la coherencia. Las dos decisiones son defendibles y ninguna es gratis.

flowchart TB
I[Idea de Elm] --> A{Se puede adoptar sin cerrar puertas}
A -->|Si| B[Se convierte en patrimonio comun]
A -->|No| C[Se queda en Elm]
B --> D[Vista pura, eventos como datos, tipos suma]
C --> E[Pureza obligatoria, sin escotillas, semver comprobado]
D --> F[La garantia pasa a ser convencion]
F --> G[Linter, revision o nadie]
style B fill:#a6e3a1,color:#11111b
style C fill:#f38ba8,color:#11111b
style G fill:#f9e2af,color:#11111b

Conviene ser franco también sobre el propio Elm, porque una lección que termine en apología no sirve de nada. El proyecto publicó su última versión mayor en 2018 y una revisión menor en 2019, y desde entonces el ritmo público ha sido casi nulo; el debate sobre la gobernanza, sobre la lentitud y sobre las restricciones impuestas al código nativo alejó a parte de la comunidad, y muchas empresas que lo habían adoptado migraron. Nada de eso invalida las ideas, del mismo modo que la suerte comercial de Smalltalk no invalidó la orientación a objetos, pero explica por qué la herencia viaja hoy sin el nombre del remitente y por qué la mayoría de quienes la usan no sabrían decir de dónde viene.

Qué hacer con esta herencia

La parte accionable de todo el recorrido cabe en una pregunta que conviene hacerse ante cada patrón que se adopta: cuando esta forma funcionaba en su casa, quién impedía que se rompiera, y en la mía quién lo impide. Hay cuatro respuestas posibles y solo la primera es tranquilizadora. La sostiene el compilador, y entonces la garantía es real y no hay que volver a pensar en ella. La sostiene una herramienta automática, y entonces es casi real, siempre que nadie pueda desactivarla sin discusión. La sostiene la revisión de código, y entonces es tan buena como el peor día del equipo. No la sostiene nadie, y entonces no es una garantía sino una intención, y el sistema producirá tarde o temprano exactamente los fallos que el patrón decía evitar.

💡
Cómo se traduce eso en decisiones concretas

Si tu arquitectura depende de que los reducers sean puros, haz que una regla automática prohíba las importaciones de red, reloj y aleatoriedad en esos ficheros, en vez de confiarlo a la revisión. Si depende de que el estado sea serializable, escribe un test que lo serialice y lo restaure en cada compilación. Si depende de la exhaustividad, activa la comprobación en el compilador y trata su incumplimiento como error y no como aviso. Cada garantía que quieras conservar necesita un mecanismo con nombre, y si no consigues nombrarlo es que no la tienes.

El ejercicio no es teórico y cabe en un fichero de configuración. Convertir una convención en una regla automática cuesta media tarde y cambia su naturaleza: deja de depender de que alguien se acuerde durante una revisión apresurada y pasa a fallar en la integración continua, que es la única forma de disciplina que no se erosiona con el cansancio ni con la rotación del equipo. Lo mismo vale para la exhaustividad, que en la mayoría de los lenguajes está detrás de una opción del compilador que muchos proyectos dejan en modo aviso por comodidad y que debería estar en modo error desde el primer día.

// Una garantia sin mecanismo es una intencion; con mecanismo, es una regla
export default {
  rules: {
    'no-restricted-imports': ['error', {
      paths: ['node:crypto', 'axios'],
      // en los ficheros de transicion, ningun efecto puede entrar
    }],
    '@typescript-eslint/switch-exhaustiveness-check': 'error',
  },
}

Y queda una última recomendación, menos técnica y probablemente más duradera. Cuando una idea se vuelve invisible, la única forma de mantenerla viva es enseñarla con su historia: no como regla que se acata, sino como respuesta a un problema que alguien tuvo antes y que volverá a aparecer. Esa es, en el fondo, la razón de ser de un nivel entero dedicado al legado de un lenguaje que casi nadie usa.

La medida del triunfo de una idea es que deje de necesitar nombre, y su peligro es exactamente el mismo

Al final de este recorrido queda una paradoja que merece formularse despacio, porque es la lección que sobrevive a Elm, a sus herederos y probablemente también a los herederos de sus herederos. Una idea técnica alcanza su máxima influencia justo cuando se vuelve invisible: cuando ya no hace falta defenderla en una reunión, cuando la documentación oficial de un framework la enuncia como si fuera de sentido común y cuando los programadores nuevos la aprenden sin sospechar que alguna vez se hizo de otra manera. Ese es el estado actual de la vista como función del estado, del evento como dato y del modelado con tipos suma, y es un triunfo completo. Pero la invisibilidad tiene un coste que casi nunca se contabiliza, y es que al perderse el nombre se pierde también el argumento. Un patrón adoptado sin su justificación se transmite como estilo, y un estilo se abandona en cuanto resulta incómodo; el equipo que introduce una vía de escritura paralela al estado central bajo la presión de una fecha de entrega no está rechazando un argumento, porque no llegó a conocerlo, sino saltándose una costumbre. Por eso la única forma responsable de heredar es conservar el par completo: no la regla sola, sino la regla junto al fallo concreto que existía antes de ella y que volverá a existir después. Escribe la vista como función del estado porque de lo contrario la pantalla y los datos se desincronizan de maneras que ningún test detecta. Modela los estados imposibles como imposibles porque de lo contrario alguien pintará un mensaje de error y una lista de resultados al mismo tiempo. Haz que el efecto sea un dato porque de lo contrario tu sistema solo será comprobable hasta el borde de lo que puedes sustituir. Y cuando adoptes cualquiera de estas formas, verifica quién paga la garantía en tu casa, porque las ideas viajan solas pero los mecanismos que las hacen ciertas nunca vienen en el equipaje. Ese es todo el legado de Elm y también, si se acepta, la única manera de no tener que redescubrirlo dentro de otros diez años con otro nombre.

⚔️ Inventario de garantías de tu propia base de código
  1. Enumera las seis ideas heredadas de esta lección y marca cuáles están presentes en tu proyecto actual, con una prueba concreta de cada una y no una impresión.
  2. Para cada una de las presentes, identifica el mecanismo que la sostiene y clasifícalo entre compilador, herramienta, revisión o nadie.
  3. Escoge la garantía peor sostenida y conviértela en automática esta misma semana, midiendo cuánto cuesta hacerlo.
  4. Busca en tu historial de incidencias tres fallos reales y determina para cada uno qué garantía perdida los hizo posibles.
  5. Redacta en cinco líneas, para tu equipo, el argumento que acompaña a una de esas reglas: el fallo que evita y por qué reaparece en cuanto se relaja.
  6. Argumenta en contra de esta lección: sostén que el pragmatismo con escotillas de escape ha producido más valor real que la coherencia sin ellas, y evalúa qué evidencia haría falta para decidir la cuestión.