Las herramientas: elm make, elm repl, elm reactor y el bucle corto
La caja de herramientas de Elm cabe en un binario y en cuatro verbos, y esa pequeñez es una decisión de diseño con consecuencias medibles. Esta lección recorre el instrumental completo empezando por el compilador como punto de entrada único, con sus modos de depuración y de optimización y con la salida estructurada que alimenta al resto de la cadena. Continúa con el bucle corto, que es la unidad de trabajo real del programador de Elm, y explica por qué su viabilidad depende de una propiedad muy concreta del compilador que casi nunca se menciona, que es la velocidad de compilación del proyecto entero. Después estudia la sesión interactiva como instrumento de exploración de tipos, el servidor de desarrollo que compila bajo demanda y el depurador que registra el historial completo de mensajes y permite volver a cualquier punto del pasado, algo que solo es posible por la arquitectura unidireccional. Y termina con la integración en el editor, donde el mismo compilador que produce el binario alimenta los diagnósticos, el formateo canónico y la lista de saltos, de modo que la herramienta deja de ser un comando que se invoca y pasa a ser un estado permanente del entorno.
Casi todos los ecosistemas modernos de desarrollo de interfaces exigen, antes de escribir una línea útil, una inversión considerable en decidir con qué se va a construir: un empaquetador entre varios, una configuración que crece durante años, una cadena de transformaciones del código fuente, un comprobador de tipos separado del compilador, un formateador con sus opciones discutidas en reuniones, un analizador estático con centenares de reglas activables y el pegamento que hace que todo eso coopere. Ese trabajo tiene un nombre que la profesión ha normalizado sin protestar, que es configurar el proyecto, y consume una fracción de tiempo que nadie contabiliza. Elm hizo la apuesta contraria y la hizo entera: un solo binario, cuatro verbos, un archivo declarativo de dependencias y ninguna opción que discutir. La consecuencia no es solo comodidad. Cuando la herramienta desaparece de la conversación, el bucle entre escribir y saber si funciona se acorta hasta volverse casi instantáneo, y ese bucle corto es, mucho más que cualquier característica del lenguaje, lo que la gente describe cuando dice que programar en Elm se siente distinto.
- Manejar el compilador como punto de entrada único, distinguiendo los modos de depuración, de optimización y de salida estructurada.
- Explicar por qué el bucle corto depende de la velocidad de compilación del proyecto completo y qué se pierde cuando esa propiedad falla.
- Usar la sesión interactiva para explorar tipos y el depurador para reconstruir el historial de mensajes de una sesión.
- Integrar el compilador en el editor de modo que los diagnósticos, el formateo y los saltos vengan de la misma fuente.
Un binario y cuatro verbos
El instrumental oficial se aprende en una tarde y no cambia entre proyectos. Un comando crea el archivo declarativo de dependencias, otro compila, otro abre una sesión interactiva, otro levanta un servidor de desarrollo y otro instala paquetes. No hay archivo de configuración del compilador, no hay complementos, no hay canalización que definir y no hay una forma alternativa de hacer lo mismo. La homogeneidad tiene un efecto que se aprecia al abrir un proyecto ajeno: se compila igual que el tuyo, con los mismos comandos y sin leer nada.
# Crear el proyecto: genera elm.json y src/
elm init
# Compilar a JavaScript
elm make src/Main.elm --output=app.js
# Modo depuracion: incluye el depurador con historial de mensajes
elm make src/Main.elm --output=app.js --debug
# Produccion: elimina codigo muerto y acorta nombres
elm make src/Main.elm --optimize --output=app.js
# Solo comprobar tipos, sin generar nada
elm make src/Main.elm --output=/dev/null
# Salida estructurada para que la consuma otra herramienta
elm make src/Main.elm --report=json
Los dos modos merecen una nota porque son excluyentes y esa exclusión es deliberada. El modo de depuración inyecta en el artefacto la maquinaria necesaria para registrar cada mensaje y cada modelo intermedio, lo cual engorda el resultado y ralentiza la ejecución; el modo de optimización elimina todo lo que no sea alcanzable, acorta los nombres internos y elimina las llamadas de rastreo. Pedir los dos a la vez no es posible, y el compilador se niega además a construir en modo optimizado si queda algún marcador de trabajo pendiente en el código, lo cual convierte un descuido en un error de compilación en el único momento en que importa.
La instalación de dependencias completa el cuadro y merece una observación. El comando de instalación no descarga lo que le pidas sin más: resuelve el conjunto entero de restricciones y se niega si no existe una combinación de versiones compatible, en lugar de dejar el proyecto en un estado que compila hoy y falla mañana. Como todas las versiones publicadas cumplen el versionado que estudiaremos en la lección siguiente, el resolutor puede razonar sobre los intervalos con certeza y no con heurísticas.
# Anadir una dependencia: resuelve restricciones o se niega
elm install elm/http
# Las dependencias directas e indirectas quedan fijadas en elm.json
El bucle corto
La unidad de trabajo real no es el comando sino el ciclo: escribir, guardar, saber. Que ese ciclo dure uno o quince segundos no es una diferencia cuantitativa sino cualitativa, porque por debajo de cierto umbral el programador no cambia de contexto y por encima sí. Elm sostiene el bucle corto gracias a una propiedad que rara vez se menciona en las comparaciones y que es probablemente su ventaja práctica más grande: el compilador reconstruye el proyecto entero en un tiempo que se mide en fracciones de segundo incluso en bases de código considerables, porque no hay que resolver sobrecargas, no hay que ejecutar código en tiempo de compilación y no hay una cadena de transformaciones intermedias.
Merece la pena detenerse en lo que esa velocidad hace posible, porque no es solo comodidad. Un compilador lento obliga a cambiar de estrategia: como preguntar es caro, el programador escribe tramos largos antes de comprobar, y al comprobar recibe varios errores mezclados cuyas causas se enredan entre sí. Un compilador instantáneo permite la estrategia contraria, que es preguntar después de cada decisión pequeña y no acumular nunca más de un problema a la vez. La diferencia entre ambas formas de trabajar no se mide en segundos ahorrados sino en cuántos hilos mentales hay que sostener simultáneamente, y esa es la variable que decide si una tarde de trabajo resulta agotadora o fluida.
-- El archivo de dependencias es declarativo y no configura al compilador
-- elm.json, en su forma minima para una aplicacion
-- type : application
-- source-directories: src
-- elm-version : 0.19.1
-- dependencies : directas e indirectas, con version exacta
Compilación completa
No hay compilación incremental parcial que deje dudas: se reconstruye todo y se responde en menos de un segundo en proyectos grandes.
Sesión interactiva
elm repl responde con el tipo de cualquier expresión. Es el instrumento más rápido para explorar una función desconocida.
Servidor de desarrollo
elm reactor sirve el proyecto y compila cada archivo bajo demanda al pedirlo, sin configuración ni empaquetador.
Un solo formato
El formateador canónico de la comunidad no tiene opciones. No hay estilo que negociar y los cambios no ensucian el historial.
Durante la mayor parte de la jornada no interesa el artefacto sino la respuesta, y por eso el comando que conviene tener asociado a una tecla no es el que produce el archivo final sino el que descarta la salida y solo comprueba tipos. Es más rápido, no toca el disco y devuelve exactamente la información que se busca. Con eso montado, el ciclo de trabajo deja de tener una fase de construcción y se convierte en una conversación: escribes tres líneas, preguntas, el compilador contesta, corriges. Quien viene de entornos donde construir es una operación que se lanza y se espera tarda un tiempo en creer que la respuesta ya está ahí.
La sesión interactiva y el depurador que viaja al pasado
La sesión interactiva de Elm no es un intérprete alternativo sino el mismo compilador respondiendo expresión a expresión, y su utilidad principal no es calcular sino preguntar tipos. Escribir el nombre de una función desconocida y leer su firma es más rápido que buscarla en la documentación, y encadenar dos funciones para ver qué tipo sale es la forma más directa de averiguar por qué una composición no encaja. Conviene recordar que las definiciones de la sesión no persisten y que hay que importar los módulos que se quieran usar.
elm repl
> import String exposing (..)
> String.split "," "a,b,c"
["a","b","c"] : List String
> import Maybe
> Maybe.andThen
<function> : (a -> Maybe b) -> Maybe a -> Maybe b
# Servidor de desarrollo con navegador de archivos en el puerto 8000
elm reactor
El depurador es la pieza que más sorprende a quien llega de otros entornos, y su existencia no es un logro de ingeniería de herramientas sino una consecuencia directa de la arquitectura. Como todo cambio de estado pasa por un mensaje, como la función de actualización es pura y como el modelo es un valor inmutable, la historia completa de una sesión es simplemente la lista de mensajes recibidos. Registrarla es trivial, reproducirla es determinista y volver a cualquier punto del pasado consiste en recalcular desde el principio. El depurador permite además exportar ese historial a un archivo y que otra persona lo importe, de modo que un informe de fallo puede consistir en la secuencia exacta de acciones que llevó al problema en lugar de una descripción aproximada.
flowchart LR E[Editor guarda] --> M[elm make comprueba tipos] M --> J[Salida estructurada] J --> D[Diagnosticos en el editor] J --> Q[Lista de saltos] D --> E M --> R[elm reactor recarga] R --> B[Depurador con historial] style M fill:#a6e3a1,color:#11111b style D fill:#89b4fa,color:#11111b style B fill:#cba6f7,color:#11111b
El editor como superficie del compilador
El último paso convierte todo lo anterior en algo que no hay que invocar. El servidor de lenguaje de la comunidad no reimplementa el análisis: llama al mismo compilador pidiéndole la salida estructurada y traduce el resultado al protocolo que el editor entiende. Eso tiene una consecuencia importante y poco habitual, y es que los diagnósticos que ves mientras escribes son literalmente los mismos que verás al construir, sin discrepancias entre lo que dice el editor y lo que dice la herramienta de integración continua.
# Piezas que conviene tener enganchadas al editor
elm-language-server # diagnosticos, ir a definicion, autocompletado
elm-format # formato canonico sin opciones, al guardar
elm-test # pruebas unitarias y basadas en propiedades
elm-review # analisis con reglas propias del proyecto
En un editor de la familia de Vim la integración natural aprovecha dos mecanismos que ya conoces de otros niveles de esta guía. El primero es el cliente de protocolo de lenguaje, que sitúa los diagnósticos junto a la línea culpable y permite saltar entre ellos sin salir del archivo. El segundo es la lista de saltos, que recibe la salida del compilador convertida en una colección de posiciones y convierte el refactor dirigido por errores de la lección anterior en un recorrido mecánico: se arregla el primero, se salta al siguiente y se repite hasta que la lista queda vacía. Añadir el formateador al guardar cierra el círculo, porque elimina de la revisión de código toda discusión que no sea sobre el fondo.
Conviene situar bien la última pieza para no pedirle lo que no hace. El compilador se ocupa de lo que es incorrecto; el analizador de la comunidad se ocupa de lo que es indeseable en este proyecto concreto, que es una categoría distinta y que ningún compilador debería imponer. Ahí caben reglas como prohibir los marcadores de trabajo pendiente en la rama principal, exigir que ciertos módulos no dependan de otros, detectar valores expuestos que nadie usa o vetar una función concreta que el equipo ha decidido abandonar. La diferencia importante es que esas reglas las escribe el equipo, viven en el repositorio y se pueden discutir, mientras que las del compilador no se discuten. Confundir ambos planos produce dos errores simétricos: pedirle al compilador que imponga estilo, y usar el analizador para tapar agujeros que en realidad se cierran modelando mejor los tipos.
La reacción habitual ante el instrumental de Elm, sobre todo viniendo de ecosistemas ricos, es leerlo como inmadurez: solo cuatro comandos, ninguna opción, ningún complemento, nada que ajustar. Vale la pena invertir la lectura, porque hay una tesis detrás y es una tesis sobre dónde conviene que se acumule la complejidad de un sistema. Toda plataforma tiene una cantidad irreducible de complejidad, y la única decisión disponible es en qué capa se deposita. Si el lenguaje es permisivo y admite muchas formas de hacer lo mismo, la complejidad se desplaza hacia arriba, hacia la herramienta, que debe ofrecer opciones para cada variante, complementos para cada caso y configuración para reconciliarlo todo; y como esa configuración vive en cada proyecto, la complejidad se multiplica por el número de proyectos y se convierte en un cuerpo de conocimiento que caduca. Si el lenguaje es estricto y define una única forma correcta, la complejidad se queda abajo, dentro del compilador, escrita una vez por quien lo mantiene y no replicada por cada equipo. Elm eligió lo segundo con una consistencia casi obsesiva, y lo que se percibe como pobreza de la caja de herramientas es en realidad la sombra de esa elección: no hay opciones de formato porque solo hay un formato, no hay complementos del compilador porque no hay ambigüedades que resolver caso por caso, no hay configuración de la construcción porque no hay construcción que configurar. El beneficio no es estético ni ideológico, es aritmético: el tiempo que un equipo dedica a decidir cómo se construye su proyecto es tiempo restado al problema por el que le pagan, y ese tiempo se paga una vez en Elm y todas las semanas en otros sitios. Y hay un segundo beneficio que solo se aprecia con los años y que es probablemente mayor: una herramienta sin opciones no puede quedarse desactualizada de forma incompatible, y por eso los proyectos de Elm de hace años siguen compilando con los comandos de siempre mientras a su alrededor ecosistemas enteros han cambiado de empaquetador tres veces. La estabilidad no fue un accidente afortunado; fue lo que se compró renunciando a las opciones.
- Asocia a una tecla del editor la comprobación de tipos sin generar salida y mide cuánto tarda en el proyecto más grande que tengas a mano.
- Compila el mismo proyecto en modo depuración y en modo optimizado, compara el tamaño de ambos artefactos y explica de dónde sale la diferencia.
- Deja un marcador de trabajo pendiente en el código, intenta construir en modo optimizado y razona por qué la negativa del compilador es una funcionalidad y no un obstáculo.
- Explora tres funciones que no conozcas usando solo la sesión interactiva y deduce lo que hacen a partir de sus firmas antes de leer la documentación.
- Reproduce un fallo con el depurador, exporta el historial de mensajes e impórtalo en otra sesión para comprobar que el estado se reconstruye.
- Conecta la salida estructurada del compilador a la lista de saltos de tu editor y recorre un refactor entero sin escribir una sola búsqueda.