2 puntos por GN⁺ 2026-06-12 | 1 comentarios | Compartir por WhatsApp
  • La razón por la que la popularidad del tipado estático cayó desde los 2000 hasta principios de los 2010 y luego volvió a subir a mediados y finales de los 2010 fue la mejora en la calidad de los sistemas de tipos estáticos, no una moda
  • Al cavar un hoyo, así como es mejor usar las manos que una pala de papel, un sistema de tipado dinámico es mejor que un sistema de tipado estático mediocre
  • Los sistemas de tipos estáticos del pasado, como Java temprano o C++98, ni siquiera podían distinguir entre nullable y no nullable, no tenían tipos suma y obligaban a escribir los nombres de tipos una y otra vez
  • Los sistemas de tipos modernos como TypeScript, Haskell, Swift y Rust ofrecen por defecto distinción de null, tipos suma/unión e inferencia de tipos
  • Con la generalización del autocompletado de métodos en los IDE, la información escrita para el tipado estático aporta productividad adicional además de la detección de errores

Hipótesis sobre el cambio en la popularidad del tipado estático

  • El fenómeno de que el tipado estático perdiera popularidad en los 2000 y principios de los 2010, y luego volviera a crecer a mediados y finales de los 2010,
    no se debe a que la programación sea una industria guiada por modas, sino a que mejoró la calidad de los sistemas de tipos estáticos de uso generalizado

La metáfora de la pala — la calidad de la herramienta define la elección

  • Al cavar un hoyo, si la pala sirve, por supuesto se usa en lugar de las manos, pero
    si la única pala disponible está hecha de papel, solo removerá la tierra inútilmente, así que es mejor usar las manos
  • En un sistema de tipado dinámico, el estado y contenido de variables y campos deben rastrearse completamente en la propia cabeza,
    y la computadora ni ayuda ni estorba, así que equivale a cavar con las manos
  • Los sistemas de tipos estáticos pobres, comunes en los 1990 y principios de los 2000, eran como una pala de papel
    • Ni siquiera ayudaban con algo tan simple como distinguir punteros nullable y no nullable
    • Solo tenían tipos producto (product type) y no tipos suma (sum type)
    • Existía la carga de escribir manualmente los nombres de tipos por todas partes
    • Código como BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); era un pequeño desastre

Lo que los sistemas de tipos modernos siempre ofrecen

  • En contraste con sistemas modernos como TypeScript, Haskell, MyPy, Swift y Rust, hoy siempre se ofrece lo siguiente
  • Distinción entre nullable y no nullable

    • Haskell ofrece Maybe t, TypeScript T | null, Swift T? y Rust Optional<T>
    • El sistema de tipos indica fácilmente todas las ubicaciones donde hace falta un chequeo de null y si alguno falta
    • En la práctica, casi no se ven errores de puntero null en tiempo de ejecución
  • Tipos suma o tipos unión

    • Hacen posible la práctica de "Make invalid states unrepresentable"
    • Permiten crear objetos de máquina de estados (state machine) con varios campos, y cada campo existe solo cuando corresponde a ese estado
  • Inferencia de tipos

    • Como el compilador puede inferir que let x = 5; es un número, no hace falta escribir let x: number = 5;

La utilidad adicional que trajo la expansión de las funciones de IDE

  • La generalización de funciones de IDE como el autocompletado de nombres de métodos aumentó la utilidad de los sistemas de tipado estático
    • En los 1990, Intellisense de Visual Studio era una función estrella, pero en los 2020 ya se dispone de algo similar en casi todos los IDE y editores
    • La información introducida en un sistema de tipos estáticos se traduce, además de revisar errores, en ventajas adicionales de productividad

Conclusión

  • Un buen sistema de tipado dinámico es mejor que un mal sistema de tipado estático
  • Aun así, ahora contamos con sistemas de tipado estático mucho mejores que los del pasado

1 comentarios

 
GN⁺ 2026-06-12
Opiniones de Lobste.rs
  • El texto está bueno, pero no estoy completamente de acuerdo. Aunque los sistemas de tipos estáticos de inicios de los 2000 no eran excelentes, igual eran mucho mejores que no tener tipos estáticos en absoluto
    No había tipos suma cerrados, pero se podía modelar bastante con subtipado, y aunque no existían los tipos no anulables, las referencias y tipos no puntero de C++, y los tipos primitivos de Java, cubrían parte de eso. En Ruby o JavaScript la situación era peor, porque no solo todos los tipos podían ser null, sino que además podían tratarse como string, como entero o como cualquier otro tipo del programa
    Creo que una razón importante del cambio de rumbo respecto a los tipos estáticos fue que, durante el boom de las redes sociales de la Web 2.0, el efecto de ser el primero importaba más que cualquier otra cosa. Aunque acumularas deuda técnica con Ruby o Python, era mejor lanzar rápido e iterar que quedar desplazado como Friendster o Digg, y si algo iba lento, en esa época bastaba con comprar más servidores con el capital barato que se conseguía fácilmente
    Después, con el boom móvil, el software pasó a ejecutarse en dispositivos de usuario limitados y fuera de tu control, y las apps lentas con tipado dinámico simplemente eran lentas de verdad, mientras que un error de tipo no podía recuperarse elegantemente con un manejador de respuesta de nivel superior como en un servidor. En ese entorno, la seguridad y el rendimiento del tipado estático se volvieron mucho más convincentes

    • Hay bastantes artículos que comparan Java y el C++ estilo noventero con codebases en lenguajes de tipado dinámico y concluyen que tienen tasas de bugs parecidas, y los partidarios de los lenguajes dinámicos suelen citarlos como prueba de que el tipado estático no sirve de mucho
      A principios de los 2000 yo también estaba de acuerdo, porque los sistemas de tipos de entonces solían imponer restricciones que ayudaban poco a estructurar el código mientras solo hacían cumplir propiedades que casi nunca fallaban. En particular, la combinación de subtipado y herencia de implementación no era flexible
      Mi opinión cambió al usar sistemas de tipos más modernos. En snmalloc, el sistema de tipos de C++ hace cumplir una máquina de estados de propiedad de memoria, y en otros codebases verifica el comportamiento correcto de overflow de contadores de buffer circular. En ambos casos, si eso sale mal, depurarlo es molesto y es una fuente común de errores; de hecho, el compilador rechazó código que yo creía correcto y evitó que bugs entraran al árbol
    • Creo que desarrollar en lenguajes de tipado dinámico es más lento que hacerlo en lenguajes de tipado estático. Sigo viendo la afirmación contraria, pero no la entiendo
      En el IDE, presionar . y teclear un poco el nombre del método para luego dar Enter en la opción correcta te ahorra 2 segundos cada pocos segundos, y también te ahorra esos 30 segundos de ir a buscar la definición de la clase cuando no sabes qué métodos tiene. Ese principio también queda muy bien explicado en https://grugbrain.dev/#grug-on-type-systems
      Uno escribe líneas que llaman métodos con mucha más frecuencia de la que escribe tipos de parámetros de funciones, así que el intercambio sale abrumadoramente mal para el tipado dinámico. Lo que sí tenía valor no era permitir código absurdo que iba a fallar en runtime, sino omitir el tipo de las variables locales, y los lenguajes estáticos nunca tuvieron por qué prohibir eso
    • Los sistemas de tipos populares de principios de los 2000 no eran simplemente “no tan increíbles”; eran malos, y muy verbosos
      Los pocos codebases que usaban el sistema de tipos en serio acumulaban páginas enteras de código que no decía nada, y aun así tenían montones de condicionales en runtime; además, en Java el programa de hecho se volvía más lento a medida que crecía la jerarquía de tipos. La mayoría de los codebases usaban los tipos a medias y agregaban muchos condicionales en runtime, por lo que tampoco ahorraban mucho frente a sistemas dinámicos en cuanto al alcance de pruebas necesarias
      Los lenguajes dinámicos no daban recompensas estáticas, pero eran concisos, fáciles de leer y revisar, y también fáciles de probar. Eso era especialmente cierto en entornos como los frameworks de inyección de dependencias de finales de los 90 e inicios de los 2000, donde agregar un servicio nuevo implicaba editar varios archivos XML. Además, podías trabajar sin un IDE que se comiera la mitad de la RAM
      Mis primeros años de carrera fueron exactamente así, así que coincido bastante con el texto. La relación costo-beneficio de Java 1.4 a Java 6 era tan mala que casi me hizo abandonar por completo los lenguajes con tipado estático; solo años después, cuando probé Haskell como hobby, entendí que el tipado estático sí podía tener una relación costo-beneficio razonable, y que el problema era Java. El ensayo “python is not java” también retrata bien esa época oscura
    • El subtipado basado en herencia era todavía más pobre. No ofrecía la usabilidad del pattern matching con verificación de exhaustividad, y la implementación quedaba fragmentada en varios lugares
    • La explicación de que era importante lanzar un sitio antes que la competencia, ponerlo frente a los usuarios y asegurar economías de escala suena bastante familiar incluso hoy
  • Dudo que, desde que el tipado estático se volvió el espíritu de la época, hayamos visto de verdad una ganancia de confiabilidad en nuestro software
    Siempre pensé que la ventaja del tipado estático estaba mucho más en la retroalimentación inmediata durante el desarrollo y en reducir fallas fatales en tiempo de ejecución, pero aunque en teoría esas fallas siempre pueden pasar, en la práctica no parecía que ocurrieran tan seguido

    • Sí lo he visto. Cuando empezamos a apuntar a 0 errores de TypeScript en un codebase de TypeScript nada trivial, se desplomaron los intentos de llamar métodos sobre undefined y null
      Al principio, los junior y algunos senior eran escépticos y pensaban que iban a aparecer @ts-ignore por todos lados, pero en la práctica fueron como tres, incluso contando los causados por tipos rotos en dependencias. Antes, más o menos una vez por semana la app se caía en la rama de desarrollo por confusiones de tipos y me bloqueaba el trabajo; ahora ni recuerdo cuándo fue la última vez que me pasó algo así
      Con solo dejar satisfecho a tsc, incluso en casos donde yo no escribí el código, bajaron los bugs relacionados con tipos. En cambio, los linters de hoy son demasiado entusiastas, y tratando de dejar contentas herramientas como Sonar vi roturas reales por refactors. El 95% de las advertencias eran falsas, 3% eran bugs de la herramienta, y el 2% que sí ayudó tampoco era la causa real del bug. En vez de dedicar una semana a ajustar el codebase y atrapar un bug, metí dos más en el proceso
      El trabajo de dejar satisfecho a tsc producía más o menos 2 correcciones de bugs puros por día y 1 regresión, pero la regresión normalmente era menos grave, de comportamiento incorrecto y no de crash total
      Si además le sumas pruebas basadas en propiedades, en promedio tomaban entre 2 y 4 horas y siempre destapaban al menos un bug. Si tu código se puede probar con pruebas basadas en propiedades, hay que hacerlo
      Al ampliar la cobertura de pruebas con un modelo barato como DeepSeek V4 Flash, cuidando que no salieran pruebas basura, arreglábamos entre 2 y 3 bugs lógicos por día, y no hubo crashes. Eso sí, el conjunto de pruebas apenas era mantenible
      Cuando dejamos que un junior armara pruebas más o menos a la rápida con modelos de la línea Sonnet y Opus 4.5, 4.6, los modelos solo generaban pruebas que “documentaban el comportamiento actual”, así que el efecto de las correcciones fue pequeño, y el conjunto de pruebas era tan inmantenible que hubo que descartarlo
      Las pruebas basadas en modelos son muy buenas para atrapar bugs, pero configurarlas es complicado, y lograr que exploren a fondo las esquinas sin gastar ciclos en funcionalidades superficiales es muy engorroso. Algo como un fuzzer basado en modelos y guiado por perfiles sería interesante
      En resumen: los verificadores de tipos atrapan bien fallas fatales y varias confusiones, y las pruebas basadas en propiedades son excelentes. Las pruebas comunes requieren mucha disciplina para dar recompensas de forma constante
    • En lo personal, diría que sí. En el JavaScript que uso, los bugs de puntero nulo se volvieron casi despreciables después de migrar a TypeScript, y mis colegas vieron algo parecido
  • Lo que más me cuesta aceptar aquí es meter a TypeScript en el mismo saco que un buen sistema de tipos

    • Sí. TypeScript no es sound, y en particular la forma en que estrecha tipos a través de await me ha mordido varias veces. Aun así, es verdad que mejoró la situación de forma dramática
      Siendo sincero, al final también terminé aceptando el tipado estructural, y creo que va a influir positivamente en el diseño de lenguajes a futuro
  • Este argumento convence poco. Los lenguajes de programación decentes con tipos algebraicos de datos e inferencia de tipos existen desde mediados de los 90
    Los sistemas de tipos de Java y C++ eran muy pobres, pero SML, OCaml y Haskell ya existían y se sentían bastante parecidos a los de hoy. Si la gente no usaba esos lenguajes, eso tiene que ver con cultura, adopción y requisitos no expresados, no se puede explicar solo con que “los sistemas de tipos utilizables no eran lo bastante buenos”
    O, si la afirmación es “los sistemas de tipos de los lenguajes populares de entonces eran malos, y los de los lenguajes populares de hoy son mejores, así que los sistemas de tipos se volvieron más populares”, suena a razonamiento circular
    También hay muchos matices en la diferencia entre lenguajes diseñados junto con un sistema de tipos y lenguajes que originalmente se diseñaron sin tipos y después recibieron uno encima

  • Incluso viniendo de alguien que siempre ha preferido el tipado dinámico, me parece que este texto es bastante justo. Hoy trabajo con C#, uso Lisp como hobby y antes también usé Python
    Cuando me tocó usar Java 5, casi siempre estaba peleando con el sistema de tipos por malas decisiones de los autores de librerías. Después de pasarme a C# alrededor de 2010, el sistema de tipos ya no era activamente dañino, pero en general era redundante, y tampoco evitaba la excepción de puntero nulo, que era la confusión de tipos más común en Python
    El sistema de tipos de C# recién empezó a ayudar de verdad alrededor de 2020, cuando llegaron los tipos de referencia no anulables. Este año también llegan los tipos unión nativos, aunque las librerías de tipos unión que fuerzan exhaustividad existen al menos desde 2016, y yo las empecé a usar en 2020
    Creo que la moda sigue influyendo, pero una parte de eso no es mala. Los lenguajes de moda con sistemas de tipos más expresivos también trajeron mejoras a los lenguajes comunes que usamos para ganarnos la vida

  • Haskell y su sistema de tipos ya existían desde los años 2000. No se usaban tan ampliamente como ahora, pero claramente existían, así que esta afirmación debería matizar esa parte.
    Personalmente, creo que TypeScript fue un factor importante para familiarizar a los usuarios de lenguajes mainstream con un mejor sistema de tipos. Además de su calidad y el respaldo de Microsoft, tenía la ventaja de aplicarse a JavaScript, y JavaScript necesitaba mucho más los tipos que Python. Por “Undefined is not a function.” y “The good parts.”

    • Ojalá saliera un libro de “good parts” adaptado a la versión moderna de JavaScript y que mantuviera la concisión.
      “Real World Haskell” salió en 2008 y su objetivo era hacer que Haskell pareciera más atractivo para los programadores mainstream. No sé cuánto ayudó a difundir la buena nueva.
      En el mundo Java, Scala trajo tipos geniales en 2004, y en .NET apareció F# en 2005. Puede que Scala haya conseguido los usuarios más visibles, como Twitter, pero no estaba en una posición para absorber una gran parte de los usuarios de esa plataforma como sí lo hizo TypeScript, ni era lo bastante atractivo como Rust o Go para atraer en masa a usuarios de otros lenguajes.
    • El artículo ya trata este problema. Compara los sistemas de tipos estáticos pobres de Java temprano o C++98, populares en los 90 y principios de los 2000, con palas de papel.
      En el párrafo siguiente menciona a Haskell como un “sistema de tipos moderno”, pero en la práctica la gente con experiencia en Haskell a fines de los 90 y principios de los 2000, incluso contando a quienes solo lo tocaron un poco, era prácticamente 0%. El artículo habla de cómo la gran mayoría de los desarrolladores experimentó los lenguajes con tipos estáticos en esa época, y de por qué esa mayoría terminó evitando colectivamente los lenguajes de tipos estáticos.
    • Creo que Haskell y OCaml sufren en cierta medida por la debilidad de su ecosistema de herramientas. El lenguaje en sí es excelente, pero pierden adopción por las innumerables pequeñas molestias del lado de las herramientas.
      Por ejemplo, para usar dune en OCaml hay que entender archivos opam, archivos dune, la sintaxis de ocaml module y la sintaxis de ocaml. Las extensiones opcionales del compilador en Haskell también se sienten igual de intimidantes.
      Contrasta con cargo, donde basta con conocer toml y Rust.