El coste de las garantías: un lenguaje sin escotillas
Todos los lenguajes con tipos fuertes acaban ofreciendo una puerta trasera por la que decirle al compilador que confíe en el programador, y todos la justifican con el mismo argumento pragmático: hay casos raros en los que el sistema de tipos se equivoca y el trabajo no puede detenerse. Elm es la excepción notable, y esta lección examina la decisión en detalle: inventaría las puertas que deliberadamente no existen, describe con precisión qué trabajo se vuelve imposible o mucho más caro por su ausencia, y reconstruye el argumento por el cual el diseño considera aceptable ese coste, que se apoya en una propiedad poco intuitiva de las escotillas: no son locales. Se cierra reconociendo dónde duele de verdad la decisión y qué válvulas parciales existen para no ahogarse.
Cuando un sistema de tipos impide algo que el programador sabe correcto, existen dos respuestas posibles y ambas tienen defensores serios. La primera consiste en abrir una puerta explícita y marcada por la que se pueda pasar bajo la propia responsabilidad, con la expectativa de que se usará poco y con cuidado; es la respuesta de casi toda la industria y tiene a su favor un argumento difícil de rebatir, que es la existencia comprobable de casos donde el compilador está equivocado. La segunda consiste en no abrir ninguna puerta y aceptar que habrá trabajos que no se puedan hacer, con la expectativa de que la ausencia de puertas produce una propiedad que ninguna cantidad de disciplina puede reproducir. Elm eligió la segunda, y lo hizo sabiendo que perdía cosas concretas y valiosas. Entender esa elección exige resistir la tentación de calificarla de purismo, porque no nace de un gusto estético sino de un análisis sobre cómo se propagan las garantías en un sistema formado por código propio, código ajeno y personas con plazos.
- Inventariar con precisión las escotillas que otros lenguajes ofrecen y que Elm decide no tener.
- Describir el trabajo concreto que se vuelve imposible o mucho más caro por esa ausencia.
- Reconstruir el argumento de la no localidad: por qué una sola escotilla degrada la garantía de todo el sistema.
- Reconocer los límites del argumento y las válvulas parciales que el diseño sí admite.
Las puertas que no existen
El inventario es más largo de lo que parece a primera vista, y cada ausencia cierra una vía distinta. No hay conversión insegura de tipos ni nada equivalente a un tipo comodín que acepte cualquier valor. No hay forma de llamar a una función de JavaScript desde una función Elm y recibir su resultado: la comunicación con el anfitrión pasa por puertos, que son asíncronos por construcción, o por elementos personalizados, que viven en la frontera del documento. Ningún paquete publicado puede incluir código JavaScript propio, restricción endurecida a propósito para que la garantía no dependa de la buena fe de terceros. No hay macros ni generación de código en tiempo de compilación. No hay reflexión. No hay excepciones que capturar. Y las herramientas de depuración que sí existen quedan prohibidas al compilar en modo optimizado, para que no se cuelen en producción.
Sin conversión insegura
Ninguna expresión permite afirmar un tipo que el compilador no haya demostrado. No hay confía en mí.
Sin llamada directa
No se puede invocar JavaScript y esperar su valor de vuelta. Solo hay envío de mensajes por puertos.
Sin código nativo en paquetes
Una biblioteca de terceros no puede incrustar JavaScript, así que no puede introducir fallos que el lenguaje prohíbe.
Sin macros ni reflexión
No hay metaprogramación que genere código ni inspección de tipos en ejecución. Lo que ves escrito es todo lo que hay.
-- No existe esto: una llamada directa que devuelva el valor
-- anchoDelTexto : String -> Float -- imposible en Elm puro
-- Existe esto: un mensaje que sale y una suscripcion que recibe
port medirTexto : String -> Cmd msg
port textoMedido : (Float -> msg) -> Sub msg
-- La consecuencia de diseno: cualquier dato del exterior
-- llega mas tarde y llega como un Msg mas del bucle.
Conviene separar dos cosas que suelen citarse juntas. La ausencia de escotillas de tipos es una decisión sobre seguridad: nada puede mentir sobre lo que es. La ausencia de metaprogramación es una decisión sobre legibilidad: el código que lees es el código que se ejecuta, sin generación intermedia que haya que reconstruir mentalmente. La primera protege al programa de sí mismo; la segunda protege al lector del programa. Comparten el mismo espíritu de sustracción, pero responden a preocupaciones distintas, y quien discuta la política haría bien en discutirlas por separado, porque los argumentos que valen para una no valen automáticamente para la otra.
Lo que eso te impide
La factura se paga sobre todo en integración. Envolver un componente de JavaScript con estado propio —un editor enriquecido, un mapa, una biblioteca de gráficos— exige un elemento personalizado y un protocolo de atributos, cuando en otro lenguaje bastaría con una llamada. Cualquier lectura del documento que en JavaScript sería inmediata, como medir un elemento antes de pintar, se convierte en un viaje de ida y vuelta que atraviesa el bucle y que llega un fotograma más tarde. Una interfaz nueva del navegador no está disponible hasta que alguien escribe el paquete o el puerto correspondiente. Y en el terreno del rendimiento no hay recurso: si un bucle concreto necesita una estructura mutable para ser aceptable, no puedes escribirla, solo puedes remodelar el problema o mover esa parte al anfitrión.
Integración cara
Cada biblioteca ajena exige protocolo propio: atributos, eventos y decodificación escritos a mano.
Lectura diferida
Medir el documento es un viaje de ida y vuelta. Lo que en JavaScript era inmediato llega un fotograma después.
Exploración lenta
Probar una interfaz nueva del navegador exige montar la fontanería antes de saber si la idea sirve.
Sin ajuste fino
No hay estructura mutable de emergencia para un bucle crítico: hay que remodelar o mover la carga al anfitrión.
-- La valvula que si existe: alojar un widget ajeno como nodo
verEditor : String -> Html Msg
verEditor contenido =
Html.node "editor-enriquecido"
[ Html.Attributes.attribute "contenido" contenido
, Html.Events.on "cambio" decodCambio
]
[]
-- Y el dato que vuelve entra por la aduana, como cualquier otro
decodCambio : Decode.Decoder Msg
decodCambio =
Decode.map ContenidoCambiado
(Decode.at [ "detail", "texto" ] Decode.string)
Un equipo que integra muchas bibliotecas de terceros pagará en Elm un sobrecoste real y medible por cada una de ellas, y ese sobrecoste no se amortiza como se amortiza la ceremonia de los tipos, porque no es una inversión en corrección sino peaje de frontera. Hay proyectos cuya naturaleza consiste casi por completo en pegar componentes ajenos; para ellos, la decisión de no tener escotillas es sencillamente descalificatoria, y sostener lo contrario sería propaganda. La defensa del diseño no consiste en negar el coste, sino en mostrar qué se compra con él y para qué clase de proyecto ese intercambio sale a favor.
Por qué una escotilla no es local
Antes de entrar en el argumento conviene formular la objeción en su versión más razonable, porque la caricatura no sirve de nada. Quien defiende las escotillas no dice que haya que usarlas siempre; dice que existen casos comprobables en los que el programador dispone de información que el sistema de tipos no puede expresar, que en esos casos el trabajo no debería detenerse, y que marcar explícitamente esas regiones concentra la revisión donde hace falta en vez de dispersarla. Es un argumento serio y funciona bien en lenguajes donde la región marcada es pequeña, auditable y culturalmente sospechosa. La respuesta de Elm no niega ninguna de esas premisas: ataca la conclusión desde otro ángulo.
Aquí está el núcleo del argumento, y es más sutil que la apelación habitual a la disciplina. Cuando un lenguaje ofrece una vía de escape, la garantía deja de ser una propiedad del lenguaje y se convierte en una propiedad del uso concreto que se hace de él. Para responder a la pregunta de si este programa puede romper en ejecución ya no basta con saber en qué lenguaje está escrito: hay que auditar el código propio, el de todas las dependencias y el de las dependencias de estas. La afirmación pasa de ser universal y verificable por construcción a ser particular y verificable por inspección, y la inspección no escala. Una sola escotilla en una biblioteca transitiva de la que nunca has oído hablar basta para que la propiedad deje de valer en todo el sistema.
flowchart TD L[Lenguaje con escotilla] --> Q[La garantia depende del uso] Q --> AU[Hay que auditar todo el arbol] AU --> NE[La propiedad no escala] E[Lenguaje sin escotilla] --> C[La garantia es del lenguaje] C --> SA[No hace falta auditar nada] SA --> ES[La propiedad se compone] style E fill:#a6e3a1,color:#11111b style L fill:#f38ba8,color:#11111b style ES fill:#cba6f7,color:#11111b
Conviene ver la asimetría con números aproximados para que no quede en abstracción. Una aplicación mediana en un ecosistema de paquetes arrastra con facilidad varios centenares de dependencias transitivas escritas por cientos de personas distintas a lo largo de una década. Afirmar que ese conjunto no contiene ni una sola conversión insegura, ni un solo acceso a una propiedad que podría faltar, ni una sola excepción sin capturar, exigiría una auditoría que nadie ha hecho jamás y que caducaría con la siguiente actualización. La afirmación equivalente en Elm no requiere auditoría alguna, porque la propiedad no depende del contenido de los paquetes sino de que el compilador no admite ese contenido. Esa es la diferencia entre una garantía que se comprueba y una que se construye, y solo la segunda sobrevive al crecimiento del proyecto.
El segundo tramo del argumento es sociológico y explica por qué la disciplina no sustituye al diseño. Una escotilla es la vía más barata bajo presión, y la presión es la condición normal del trabajo. Cuando faltan dos horas para una entrega y el sistema de tipos se interpone, el tipo comodín no es una tentación sino la salida racional para ese individuo en ese momento; el coste de usarlo lo pagará otra persona en otro trimestre. Por eso las escotillas que se diseñan para casos excepcionales tienden estadísticamente a normalizarse, y por eso los equipos que las usan poco lo consiguen a base de reglas, revisiones y configuraciones adicionales, es decir, reconstruyendo con trabajo humano continuo la propiedad que el diseño podía haber regalado.
El diseño no es hermético, y confundirlo con un muro sin aberturas lleva a conclusiones equivocadas. Los puertos permiten cualquier cosa que JavaScript pueda hacer, con la única condición de que la comunicación sea asíncrona y de que el dato entre validado. Los elementos personalizados permiten alojar componentes ajenos en el árbol del documento. Las banderas permiten inyectar configuración al arrancar. La diferencia con una escotilla clásica es de dirección y de forma: no hay ningún camino por el que el exterior entre en una función pura, y no hay ningún punto en el que un valor pueda mentir sobre su tipo dentro del programa. La frontera existe y está abierta, pero se cruza siempre por la aduana.
Conviene añadir que la propia existencia de la puerta cambia el diseño de las bibliotecas aunque nadie la use. Cuando un autor sabe que puede resolver un caso difícil con una vía de escape, el incentivo para encontrar una formulación que el sistema de tipos acepte se debilita, y el ecosistema acumula soluciones que funcionan sin ser expresables. Cuando esa puerta no existe, la dificultad se resuelve arriba, en el diseño del tipo, y el resultado queda disponible para todos. Esa presión constante hacia el modelado explica por qué la biblioteca de un lenguaje sin escotillas tiende a tener menos funciones y mejores tipos que la de un lenguaje comparable que sí las ofrece.
Dónde duele de verdad
Ser honesto con la decisión exige señalar los casos en los que la ausencia de escotillas produce un coste que ninguna válvula compensa. La abstracción sobre contenedores es el más citado: sin clases de tipos no se puede escribir una función genérica que funcione para cualquier estructura que admita un mapeo, y el resultado es repetición de código en cada módulo. La medición síncrona del documento es el segundo, porque hay interacciones —arrastrar, animar con posiciones calculadas, sincronizar desplazamientos— en las que un fotograma de retraso se percibe. El tercero es la exploración: probar una interfaz experimental del navegador exige montar la fontanería antes de saber siquiera si la idea vale. Un lenguaje que no ofrece atajos convierte el prototipado descuidado en algo caro, y el prototipado descuidado es a veces la herramienta correcta.
Merece la pena registrar que estas carencias no son deudas pendientes de saldar sino resultados de un método: cuando aparece una necesidad, la respuesta por defecto no es añadir una construcción al lenguaje sino examinar si el problema puede resolverse remodelando el dominio con lo que ya existe. El método acierta más veces de las que uno espera, porque muchas peticiones de abstracción nacen de una duplicación que en realidad señalaba dos conceptos distintos con el mismo nombre. Y falla en los casos en los que la necesidad era genuina, dejando al usuario con una repetición que no puede eliminar. Reconocer ambas mitades es la única forma honesta de evaluar el método, y quien solo ve una de las dos está haciendo propaganda o ajuste de cuentas.
Queda una consideración final antes del argumento de fondo, y tiene que ver con quién paga el coste dentro de un equipo. La ausencia de escotillas redistribuye el trabajo: quien construye la frontera —los puertos, los decodificadores, los elementos personalizados— hace un esfuerzo concentrado y desagradable al principio del proyecto, mientras que quien escribe funcionalidad después trabaja en un entorno donde nada puede fallar de forma inesperada. Esa redistribución es políticamente incómoda porque el esfuerzo es visible y el beneficio es difuso, y explica buena parte del rechazo temprano de los equipos: el coste se paga en la primera semana y la recompensa llega en el segundo año, que es exactamente el reparto que peor toleran las organizaciones que miden por entregas.
Conviene desarmar el argumento pragmático a favor de las escotillas en su versión más fuerte, porque no es tonto: dice que la mayoría del código no las necesita, que quien las use asume su responsabilidad, y que a cambio ningún trabajo queda bloqueado. La respuesta no consiste en dudar de la disciplina de quien lo dice, sino en observar dónde reside la propiedad de la que hablamos. Cuando la seguridad depende del comportamiento de todos los participantes, la afirmación que puedes hacer sobre tu programa cambia de naturaleza: pasas de poder decir esto no puede ocurrir a poder decir esto no debería ocurrir si nadie se equivocó, y la segunda frase, por muy alta que sea la probabilidad de ser cierta, no sirve para lo que sirve la primera. La primera te permite dejar de pensar en toda una categoría de fallos y reinvertir esa atención; la segunda te obliga a mantener encendida la vigilancia, porque el sistema podría estar mintiéndote y no hay forma barata de comprobarlo. La diferencia entre ambas no es de grado, es de tipo, y es exactamente la diferencia entre un compilador que demuestra y un equipo que promete. Por eso el argumento de Elm no es que los programadores sean indisciplinados, sino que una propiedad que depende de la disciplina de un número creciente de desconocidos no es una propiedad sobre la que se pueda construir. La conclusión práctica es incómoda y honesta a la vez: si tu proyecto necesita la puerta, no discutas con la filosofía, elige otra herramienta; y si tu proyecto puede vivir sin ella, entiende que lo que compras al renunciar no es elegancia, sino la capacidad de afirmar algo verdadero sobre tu programa sin haber leído el código de nadie más.
- Inventaría las escotillas del lenguaje que más usas y busca en tu proyecto cuántas veces aparecen; clasifica cada aparición en necesaria, cómoda o rendición.
- Elige una biblioteca de JavaScript que uses a diario y diseña sobre papel el puerto y el elemento personalizado que harían falta para envolverla.
- Escribe la mejor defensa del argumento pragmático a favor de las escotillas y localiza el punto exacto donde el argumento de la no localidad muerde.
- Describe una tarea real que hoy resolverías con una conversión insegura y remodélala hasta que el sistema de tipos la acepte sin trampas.
- Estima el sobrecoste de frontera de tu proyecto si lo escribieras en Elm, contando integraciones y no líneas.
- Formula el criterio de decisión: qué característica concreta de un proyecto lo hace incompatible con un lenguaje sin escotillas.