1 puntos por GN⁺ 2023-07-09 | 1 comentarios | Compartir por WhatsApp
  • Se plantea la necesidad de que TypeScript emita información de tipos en tiempo de ejecución y se recopila una lista de Type Mapping, Code Generation / External Tool y proyectos Adapter que buscan esquivar este problema
  • El problema central es que, sin un sistema de tipos reflexivo, manejar la serialización y la validación requiere un boilerplate interminable o generación de código bespoke basada en archivos de esquema
  • Como soluciones alternativas se proponen io-ts, zod y otros, pero resulta incómodo tener que volver a declarar los tipos al estilo de cada librería, además de que estas librerías no pueden soportar todas las capacidades de tipos de TypeScript
  • Aunque se reconoce que la eliminación de tipos de TypeScript tiene la ventaja de permitir que proyectos JavaScript usen el JavaScript emitido sin necesitar conocimiento de TypeScript, se argumenta que la información de tipos podría emitirse en forma de tablas de consulta separadas del código
  • Junto con la petición de que esto no se resuelva con decoradores, se proponen enfoques como una función de orden superior reconocida por el compilador como typescript.generateRuntimeType<T>(), los Type Providers de F# y los Source Generators de C# para permitir el uso de interfaces y el soporte de tipos de librerías externas
  • Como discusión previa relacionada, se enlaza un issue de GitHub de hace 8 años y se pide que, si hay proyectos que hayan sufrido el mismo problema, se envíe un PR para agregarlos a la lista sin importar la cantidad

1 comentarios

 
GN⁺ 2023-07-09
Comentarios de Hacker News
  • Tuve que leer la explicación del problema unas 4 veces para entender qué estaba pidiendo, y este caso muestra exactamente por qué la escritura concisa es importante
    La petición real parece ser seguridad de tipos en tiempo de ejecución, pero eso no parece muy probable porque TypeScript ha trazado la línea de no convertirse en un runtime alternativo o extendido de JavaScript. TypeScript compila a JS y sale de escena; lo que ocurra después en V8, etc., queda fuera de su alcance.
    Decir que “TypeScript debería emitir información de tipos en tiempo de ejecución” se parece más a pedir un producto nuevo bastante distinto del TypeScript actual

    • Más que pedir seguridad de tipos en tiempo de ejecución en sí, se parece a pedir que en tiempo de compilación se haga reflection sobre los tipos para generar valores y que esa información pueda usarse en runtime
      Por ejemplo, sería bueno poder crear una función genérica validate que reciba cualquier interfaz y objeto y los valide. Con reflection en tiempo de compilación podría generarse código de validación JS por tipo, o quizá pasar T como argumento en runtime para compararlo, pero dentro de la filosofía actual de TypeScript eso parece difícil
    • Con solo ver el título ya supe que era una función que quería desde hace mucho tiempo, y se parece más a pedir algo como https://github.com/rbuckton/reflect-metadata de forma más oficial y con mejor soporte
      TypeScript sabe mucha información de tipos durante la compilación, pero cuando termina de compilar la descarta. Podría exportar esa información a un archivo o dejarla como metadata en Reflect, y aun así la tira; eso parece una limitación especialmente lamentable si se considera la naturaleza de JavaScript, donde “todo es objeto”.
      Aunque no se obtenga una seguridad de tipos de TS totalmente exacta, igual se podrían agregar algunas comprobaciones con getter/setter o con Proxy de ES2015, y si la información de tipos quedara disponible en runtime se abrirían varias posibilidades interesantes como metadata ejecutable
    • No es cierto que sea imposible solo porque TypeScript sea un compilador. Bastaría con soportar una librería de reflection que emita la información de tipos como objetos JS y permita consultarla en runtime
      TS enum ya se emite como un objeto JS y puede consultarse en runtime, pero los tipos unión de literales de cadena no. Además, TypeScript ya soporta funciones type guard, así que tampoco parece tan difícil generar este tipo de funciones automáticamente usando la información que ya existe dentro del sistema de tipos
    • Incluso dándole una mirada rápida, la petición era clara. Lo que se pide es que TypeScript exporte, como un canal auxiliar, la información de tipos que descubre durante la eliminación de tipos junto al JavaScript generado
      Se puede pensar con una analogía como los archivos PDB. Es información que TypeScript ya tiene y luego desecha, así que no haría falta un producto completamente nuevo
    • En la parte superior del README hay un enlace a un “issue de GitHub de hace 7 años”, y ahí el problema se explica de forma más directa
      https://github.com/microsoft/TypeScript/issues/3628
  • Desde la perspectiva del PM de TypeScript, entiendo el deseo. La validación de datos suele requerir comprobaciones de tipos en tiempo de ejecución, y hay muchas librerías para cubrir ese vacío
    Pero el simple hecho de que existan tantas librerías con decisiones de diseño distintas ya indica que este problema no es uno resuelto con una respuesta clara. En el diseño inicial de TypeScript ya se sabía esto, y me parece que ese principio se ha mantenido bien.
    En cambio, TypeScript sí se ha vuelto lo bastante potente como para expresar con tipos lo que realmente hacen las librerías de comprobación de tipos en runtime, y el usuario puede construir lógica de validación en runtime a partir de los tipos mediante APIs. Ese nivel de flexibilidad se siente razonable

    • No llevo mucho tiempo programando, pero me pregunto por qué TypeScript no puede crear tipos de datos definidos por el usuario más sofisticados
      Por ejemplo, imagino un lenguaje donde uno pueda definir como tipo un número dentro de cierto rango o una cadena que cumpla el patrón de un código postal, y el compilador use funciones de validación escritas como funciones normales para verificar su validez. Me da curiosidad si que no exista algo así es una decisión de diseño para evitar complejidad innecesaria o si se debe a limitaciones técnicas como el rendimiento
    • Tener un plugin de preprocesador oficial para el compilador de TypeScript podría ayudar
      Incluso ahora, quien lo necesite podría crear un preprocesador que genere objetos de runtime a partir de la información de tipos antes de pasárselos a tsc, pero ese esfuerzo está disperso y cada quien lo resuelve por su lado. Si hubiera un preprocesador oficial con arquitectura de plugins y un ecosistema alrededor, podría encontrarse una buena solución
    • Si Microsoft alojara MacroScript como plugin de TypeScript o como wrapper de nivel superior, eso podría resolver este problema
      Si solo se enciende la chispa, la comunidad ayudaría con el mantenimiento, y podría cubrir varias necesidades de generación de código, desde generación de clientes hasta aserciones de tipos en runtime
    • Me pregunto qué opinan sobre otra propuesta de dejar la información de tipos en el objeto Class después de compilar
  • Hay una razón para no hacer esto. TypeScript terminaría convirtiéndose en algo así como un runtime sobre JavaScript, y en un nuevo lenguaje que compila a JS
    Ahora mismo, TypeScript es más cercano a JavaScript con anotaciones de tipo. Ya hay muchos lenguajes que compilan a JS, así que se puede usar uno de esos. Quienes quieren TypeScript con tipos en runtime parecen querer escribir código más al estilo Java/OOP que JavaScript, pero JavaScript es un lenguaje de tipado dinámico, y eso también es una ventaja

    • No necesariamente. Si un macro como generateTypeInfo!() se expandiera a un objeto de JS que codifique el tipo Foo, seguiría compilando a JavaScript legible
      Eso sí, ese macro rompe la propiedad de “TS = JS con anotaciones de tipo y compilar solo requiere quitar las anotaciones”. Porque habría que calcular de verdad el tipo estructural de Foo.
      Además, los tipos de TypeScript son estructurales, así que si inspeccionas la estructura del objeto en runtime puedes saber el tipo hasta cierto punto, pero si exiges información nominal borrada o nombres de tipos, como si pertenece a una unión de cadenas, enseguida se vuelve un problema difícil por la completitud de Turing del sistema de tipos de TypeScript y las conversiones estructurales implícitas
    • Se pueden tener ambas cosas. Se puede imaginar un mundo donde código y datos estén cerca, como la homoiconicidad de Lisp
      Hablando en serio, incluso sin runtime, si expones los tipos como datos puedes obtener reflexión. Viendo cuántos desarrolladores se han esforzado por imitar esto, no hacerlo hasta parece una tontería
    • No entiendo mucho esa lógica. TypeScript ya es un nuevo lenguaje superconjunto que compila a JS
      Los tipos en runtime resuelven el problema de tener que mantener duplicados, por separado, un sistema de tipos para compilar y otro para validar datos, y no parece tener relación directa con OOP. io-ts, que se suele preferir como biblioteca para rodear este problema, también se apoya fuertemente en la programación funcional
    • En proyectos lo bastante grandes, los lenguajes de tipado dinámico no son buenos y se parecen más a un desastre enredado donde uno sigue tropezando con valores inesperados como NaN
      En los flujos de trabajo que he visto, TypeScript ya es un lenguaje que compila a JS, así que mejor aprovechar esa ventaja al máximo
    • TypeScript no es simplemente JavaScript con anotaciones de tipo. Puede ser uno de sus modos, pero también tiene funciones para emitir estructuras de JS antiguas, así que el código TS original puede verse totalmente distinto
      Es más cercano a anotaciones de tipo + Babel, y añadirle una función más muy útil me parece buena idea. Es raro no poder convertir de forma segura una cadena parseada con JSON.parse a una estructura tipada, y en la mayoría de otros lenguajes eso sí se puede
  • Antes que lo de abajo, este es un tema válido que da perfectamente para debatir, y no creo que haya objetivamente una sola respuesta correcta
    TypeScript es una capa opcional sobre JavaScript, salvo la excepción de que Enum exporta objetos, y el código TS se vuelve JS con solo borrar los tipos. Mientras no se desvíe demasiado de ese principio, lo que en realidad se pide se parece más a una biblioteca que genere serializadores/validadores a partir de tipos. Bibliotecas así ya hay muchas, y al final esto parece más una petición para convertir una de ellas en la opción estándar oficial.
    Personalmente, no quisiera que el lenguaje central de TypeScript incorporara reflexión en runtime. Valoro que en runtime solo exista JavaScript, y que incluso sin sourcemaps el JS generado siga siendo legible y fácil de depurar

    • Además de Enum, hay otras excepciones que emiten código en runtime, y la mayoría también se consideran errores. Por ejemplo, module y namespace eran estructuras de runtime
      Aun así, en los últimos años se usaron principalmente dentro del propio TypeScript, y TypeScript también se ha ido alejando de eso. También hay una excepción bastante popular: las propiedades de parámetro, una sintaxis para definir tipos de miembros de clase desde los parámetros del constructor, que parece haber recibido menos críticas porque reduce bastante el boilerplate repetido
    • Decir que “el código TS se vuelve JS sin transformación” es posible si entiendes así el lenguaje completo y la notación de tipos
  • En vez de suplicarles a los dioses de TypeScript, quizá sería mejor negociar con el lado de JavaScript para meter verificación de tipos en JS
    Si necesitas tipos en runtime, usa type guards, y si lo necesitas seguido, usa io-ts o zod para escribir los tipos como validadores/códecs/esquemas. Hasta que exista una forma consensuada en JavaScript, no creo que la especificación de TS, el verificador de tipos y la comunidad deban cargar con la validación en runtime

    • No es perfecto, pero estoy bastante satisfecho con el enfoque de usar esquemas y validadores con zod y derivar los tipos desde ahí. Me gusta que la validación evolucione junto con los cambios de tipos
      Hay quienes sienten que una función así debería entrar al lenguaje, pero como cada biblioteca tiene muchas decisiones de diseño discutibles, puede que sea mejor poder elegir una implementación según las necesidades del proyecto.
      Eso sí, patrones como la validación en runtime de zod y la derivación de tipos basada en esquemas no siempre encajan bien con el pattern matching de ts-pattern. Las definiciones de tipos de estas bibliotecas son como un laberinto, y a veces código que parece que debería funcionar no se comporta como uno espera. Sería buenísimo que la seguridad en runtime y el pattern matching con verificación de exhaustividad se combinaran de forma fluida
    • Tengo serias dudas de que realmente sea buena idea meter una función así en JavaScript
      Siento que Promise, los decoradores y el nuevo operador pipe tomaron una dirección de implementación equivocada
  • Recuerdo que uno de los desarrolladores de TypeScript dijo que, si pudiera empezar de nuevo, no habría metido enum. Porque es la única función que emite código en runtime
    TypeScript no cambia el comportamiento en runtime, y tampoco tiene algo especial de TypeScript como {#if}

    • Eso es verdad. No sé si esa era la única razón, pero sin duda era una de ellas
      Aun así, TypeScript tiene varias funciones que van más allá de “JavaScript + anotaciones de tipo”. Están namespace, la sintaxis para definir propiedades de clase en los argumentos del constructor, los decoradores experimentales antiguos, y la sintaxis del parámetro this, que desaparece al compilar pero parece más que solo poner anotaciones de tipo sobre funciones de JS
  • Un mejor título sería algo más cercano a “TypeScript, por favor ofrece reflexión/tipos en runtime”
    La mejor solución actual probablemente sea emitDecoratorMetadata. https://www.typescriptlang.org/tsconfig#emitDecoratorMetadata

  • Parece que el autor malinterpretó los objetivos de diseño de TypeScript. La meta no es producir un JS limpio y sin complejidad, sino mantener la semántica en tiempo de ejecución de TypeScript idéntica a la de JavaScript
    Salvo la lamentable excepción de enum, TypeScript se convierte en JavaScript simplemente eliminando las anotaciones de tipo. Todo el ecosistema alrededor de TypeScript depende de una eliminación completa de tipos, y si se aceptara esta propuesta, el soporte de TS en ESBuild, Deno y Bun podría volverse casi imposible. Eso sería porque cada uno tendría que reimplementar todo tsc en su propio lenguaje.
    En cambio, las bibliotecas complejas de las que se queja el OP están implementadas en espacio de usuario, así que sí son compatibles con estas herramientas

    • Hay muchas formas de exportar información de tipos en tiempo de ejecución de manera estática, sin necesidad de un runtime. Se podría convertir keyof en una lista de claves de la clase, o hacer que las clases de TS inicialicen las claves con undefined igual que las clases de ES6
      Ahora mismo, las clases de TypeScript eliminan todas las claves que no se definan explícitamente, así que si llamas Object.keys() sobre una instancia nueva, no sale nada. Con solo tener una opción en tsconfig.json para transformar las clases de TS para que se comporten como clases de ES6, o permitir clases de ES6 directamente dentro de TS, la generación de código sería muchísimo más sencilla.
      Además, una sintaxis estática como tstypeof Foo::bar que compilara a "string" sería excelente. Incluso con RTTI básico, podría desaparecer de inmediato bastante código TypeScript sucio y repetitivo
  • Me da gusto ver este post. Empecé con TypeScript en 2018 y, unos dos años después, empecé a creer que esto no era una solución completa en esencia
    Vengo de Haskell/C#/F#, y TS, incluso con un sistema de tipos potente, no ofrece muchas de las ventajas que dan esos lenguajes fuera de ciertas partes del desarrollo. Al lidiar con el mundo real, en la práctica es mucho más limitado que C#. Si no sabes que al compilar termina convertido en JS sin verificaciones posteriores, tienes que estar siempre atento a las abstracciones con fugas para evitar bugs que no deberían ser posibles en una herramienta que dice ofrecer tipado estático

    • El propósito de TypeScript siempre fue ejecutarse en la web, no darle una superioridad de lenguaje frente a C#
      Si tienes que desarrollar en .NET, usas C#; si tienes que desarrollar para la web, usas TypeScript. C# tiene tipado nominal y genéricos reificados bastante decentes, mientras que TypeScript tiene tipado estructural y, a cambio de renunciar a la solidez, un sistema de tipos poderoso para expresar relaciones de tipos complejas.
      Dudo que un lenguaje que combine ambos resulte bueno, y aunque ambos funcionan bien en sus respectivas direcciones, esas direcciones no encajan bien entre sí
  • Para validación de datos en runtime, he quedado completamente satisfecho usando https://zod.dev
    Me pareció bastante elegante poder expresar la intención ahí mismo con una API fluida, sin tener que definir por separado tipos nominales adicionales

    • Los tipos nominales sirven para otra cosa
      Un ejemplo simple es que se necesitan para distinguir en el dominio entre el número 2 y un monto monetario de 2