TypeScript precisa emitir informações de tipo em runtime - Demanda por emissão de informações de tipo em runtime no TypeScript
(github.com/akutruff)- Uma lista que defende a necessidade de o TypeScript emitir informações de tipo em runtime e reúne projetos de Type Mapping, Code Generation / External Tool e Adapter que contornam esse problema
- O problema central é que, ao lidar com serialização e validação sem um sistema de tipos reflexivo, acaba sendo necessário escrever um volume interminável de boilerplate ou fazer geração de código bespoke baseada em arquivos de esquema
- Como alternativas, são citados io-ts, zod e outros; o inconveniente é que é preciso redeclarar os tipos no formato específico de cada biblioteca, e essas bibliotecas não conseguem dar suporte a todos os recursos de tipos do TypeScript
- Embora reconheça como vantagem o apagamento de tipos do TypeScript, que permite que projetos JavaScript usem o JavaScript emitido sem conhecimento de TypeScript, o texto argumenta que seria possível emitir informações de tipo em forma de tabelas de consulta separadas do código
- Com um pedido para que isso não seja resolvido com decorators, propõe abordagens como uma função de ordem superior reconhecida pelo compilador, como
typescript.generateRuntimeType<T>(), Type Providers do F# e Source Generators do C#, para dar suporte ao uso de interfaces e a tipos de bibliotecas externas - Liga uma discussão relacionada em uma issue do GitHub de 8 anos atrás e pede que, se um projeto tiver enfrentado o mesmo problema, envie um PR para adicioná-lo à lista, independentemente do número de estrelas
1 comentários
Opiniões no Hacker News
Precisei ler a descrição do problema umas 4 vezes para entender o que estava sendo pedido, e casos assim mostram por que escrever de forma concisa é importante
O pedido real parece ser segurança de tipos em tempo de execução, mas o TypeScript sempre traçou a linha de que não pretende se tornar um runtime substituto/adicional para JavaScript, então isso parece pouco provável. O papel do TypeScript é compilar para JS e sair de cena; o que acontece depois no V8 e afins fica fora do escopo.
O pedido de que “o TypeScript deve emitir informações de tipo em runtime” está mais próximo de pedir um produto novo bem diferente do TypeScript atual
Por exemplo, seria bom poder criar uma função
validategenérica que receba uma interface e um objeto arbitrários e faça a validação. Talvez fosse possível gerar código JS de validação por tipo via reflexão em tempo de compilação, ou passarTcomo argumento em runtime para comparar, mas acho que isso é difícil dentro da filosofia atual do TypeScriptO TypeScript sabe muita coisa sobre os tipos durante a compilação, mas descarta essas informações quando a compilação termina. Mesmo podendo exportá-las para um arquivo ou deixá-las como metadados em
Reflect, descartá-las é uma limitação lamentável, especialmente considerando a natureza do JavaScript, em que “tudo é objeto”.Mesmo que não se obtenha segurança de tipos exata do TS, dá para adicionar algumas verificações com getters/setters ou
Proxydo ES2015; deixar informações de tipo em runtime abriria várias possibilidades interessantes como metadados executáveisO
enumdo TS já é emitido como objeto JS e pode ser consultado em runtime, mas tipos union de literais de string não. Além disso, o TypeScript oferece suporte a funções de type guard, então não parece tão difícil gerar automaticamente esse tipo de função usando informações presentes no sistema de tiposDá para pensar na analogia de arquivos PDB. Como são informações que o TypeScript já tem e depois descarta, não é preciso um produto completamente novo
https://github.com/microsoft/TypeScript/issues/3628
Do ponto de vista de um PM do TypeScript, entendo a necessidade. Validação de dados frequentemente exige checagem de tipos em tempo de execução, e há muitas bibliotecas para cobrir essa lacuna
Ainda assim, o próprio fato de existirem muitas bibliotecas com decisões de design diferentes é um sinal de que isso não é um problema resolvido com uma resposta óbvia. Isso já era conhecido no design inicial do TypeScript, e acho que esse princípio se sustentou bem.
Em vez disso, o TypeScript ficou poderoso o bastante para expressar com precisão, em tipos, o que bibliotecas de checagem de tipos em runtime realmente fazem, e usuários conseguem compor lógica de validação em runtime a partir dos tipos por meio de APIs. Esse nível parece uma flexibilidade razoável
Por exemplo, imagino uma linguagem em que seja possível definir como tipo um número dentro de um determinado intervalo ou uma string que satisfaça um padrão de CEP, e em que o compilador use uma função de validação escrita como uma função comum para verificar a validade. Fico curioso se a ausência desse recurso é uma decisão de design para evitar complexidade desnecessária ou uma limitação técnica, como desempenho
Mesmo hoje, quem precisa pode criar um pré-processador que gere objetos de runtime a partir de informações de tipo antes de passar tudo para o
tsc, mas os esforços ficam espalhados. Com um pré-processador oficial plugável e um ecossistema, boas soluções poderiam ser descobertasBastaria acender a faísca para a comunidade ajudar na manutenção, e isso atenderia a várias necessidades de geração de código, da geração de clientes a asserções de tipos em runtime
Classapós a compilaçãoHá motivos para não fazer isso. O TypeScript acabaria virando algo como um runtime em cima do JavaScript, e uma nova linguagem compilada para JS
Hoje, o TypeScript é mais próximo de JavaScript com anotações de tipo. Já existem muitas linguagens que compilam para JS, então dá para usar uma delas. Pessoas que querem TypeScript com tipos em runtime parecem querer escrever código mais no estilo Java/OOP do que JavaScript, mas JavaScript é uma linguagem de tipagem dinâmica, e isso também é uma vantagem
generateTypeInfo!()se expandisse para um objeto JS que codifica o tipoFoo, ainda compilaria para JavaScript legívelSó que essa macro quebraria a propriedade de que “TS = JS com anotações de tipo, e compilar é só remover as anotações”. Isso porque seria preciso calcular de fato o tipo estrutural de
Foo.Além disso, como os tipos do TypeScript são estruturais, ao inspecionar a estrutura de um objeto em runtime dá para inferir o tipo em certa medida; mas, se você exigir informações nominais apagadas, como saber se pertence a uma união de strings, ou exigir nomes de tipos, isso rapidamente vira um problema difícil por causa da completude de Turing do sistema de tipos do TypeScript e das conversões estruturais implícitas
Falando sério, mesmo sem runtime, se você expuser tipos como dados, obtém reflexão. Vendo quantos desenvolvedores se esforçam para imitar isso, não fazer parece até tolice
Tipos em runtime resolvem o problema de ter que manter duplicados, separadamente, um sistema de tipos para compilação e outro para validação de dados, e isso não parece ter relação direta com OOP. O
io-ts, uma biblioteca preferida para contornar esse problema, também se apoia fortemente em programação funcionalNaNNos workflows que vi, TypeScript já é uma linguagem compilada para JS, então acho melhor aproveitar esse benefício ao máximo
Ele é mais próximo de anotações de tipo + Babel, e adicionar a isso um recurso muito útil é uma boa ideia. É estranho não ser possível transformar com segurança uma string vinda de
JSON.parseem uma estrutura tipada; outras linguagens em geral permitem issoAntes do que vem abaixo, este é um tema válido, com bastante espaço para debate, e não acho que exista objetivamente uma única resposta correta
O TypeScript é uma camada opcional sobre o JavaScript, exceto pela exceção de
Enumemitir objetos, e código TS vira JS se você apenas remover os tipos. Se não fugirmos muito desse princípio, a demanda real se parece mais com uma biblioteca que crie serializadores/validadores a partir de tipos. Já existem muitas bibliotecas assim, e no fim parece uma demanda para tornar uma delas a opção oficial padrão.Pessoalmente, não quero reflexão em runtime entrando na linguagem central do TypeScript. Em runtime, quero que exista apenas JavaScript, e valorizo o fato de o JS emitido ser legível e depurável mesmo sem sourcemaps
Enum, há outras exceções que emitem código em runtime, e a maioria também é considerada erro. Exemplos clássicos erammoduleenamespace, que eram estruturas de runtimeNos últimos anos, porém, isso foi usado principalmente pelo próprio TypeScript, e o TypeScript também tem se afastado disso recentemente. Outra exceção bastante popular são as propriedades de parâmetro, uma sintaxe para definir tipos de membros de classe nos parâmetros do construtor; ela parece ter escapado de menos controvérsia por reduzir boilerplate duplicado
Em vez de implorar aos deuses do TypeScript, talvez seja melhor negociar do lado do JavaScript para colocar verificação de tipos no JS
Se precisar de tipos em runtime, use type guards; se precisar disso com frequência, use
io-tsouzode escreva tipos como validadores/codecs/schemas. Até que exista uma forma consensual em JavaScript, não acho que a especificação do TS, o type checker e a comunidade devam assumir a validação em runtimezode derivar os tipos a partir deles. Gosto do fato de a validação evoluir junto com as mudanças de tipoAlgumas pessoas sentem que esse tipo de recurso deveria entrar na linguagem, mas há muitas escolhas de design controversas entre as bibliotecas, então pode ser melhor escolher a implementação de acordo com as necessidades do projeto.
Dito isso, padrões como validação em runtime com
zode derivação de tipos baseada em schema nem sempre combinam bem com pattern matching dots-pattern. As definições de tipo dessas bibliotecas são um labirinto, e às vezes um código que parece que deveria funcionar não se comporta como esperado. Seria muito bom se segurança em runtime e pattern matching com verificação de exaustividade se integrassem de forma fluidaSinto que
Promise, decoradores e o novo operador de pipe seguiram todos por uma direção de implementação erradaLembro que um dos desenvolvedores do TypeScript disse que, se fosse começar de novo, não teria colocado
enum. Isso porqueenumé o único recurso que emite código em runtimeTypeScript não altera o comportamento em runtime e não tem nada especial específico de TypeScript como
{#if}Ainda assim, o TypeScript tem alguns recursos que vão além de “JavaScript + anotações de tipo”.
namespace, a sintaxe para definir propriedades de classe em argumentos do construtor, os antigos decoradores experimentais, a sintaxe de parâmetrothis, que desaparece na compilação mas parece mais do que apenas uma anotação de tipo em uma função JS, e assim por dianteUm título melhor seria algo mais próximo de “TypeScript, por favor forneça reflexão/tipos em runtime”
A melhor solução atual provavelmente parece ser
emitDecoratorMetadata. https://www.typescriptlang.org/tsconfig#emitDecoratorMetadataO autor parece ter entendido mal os objetivos de design do TypeScript. O objetivo não é gerar uma saída JS limpa e sem complexidade, mas sim garantir que a semântica em tempo de execução do TypeScript permaneça idêntica à do JavaScript
Tirando a lamentável exceção de
enum, TypeScript vira JavaScript apenas removendo as anotações de tipo. O ecossistema ao redor do TypeScript depende de apagamento completo de tipos, e, se essa exigência fosse aceita, o suporte a TS no ESBuild, Deno e Bun poderia se tornar praticamente impossível. Isso porque cada um teria de reimplementar otscinteiro em sua própria linguagem.Por outro lado, as bibliotecas complexas das quais o OP reclama são implementadas no espaço do usuário, então são compatíveis com essas ferramentas
keyofem uma lista de chaves de uma classe, ou fazer com que classes TS inicializem chaves comoundefined, como classes ES6Hoje, as classes TypeScript removem todas as chaves que não foram definidas explicitamente, então, se você chamar
Object.keys()em uma nova instância, nada aparece. Só de transformar classes TS como classes ES6 por meio de uma opção notsconfig.json, ou de permitir classes ES6 dentro do TS, a geração de código já ficaria muito mais fácil.Além disso, seria excelente se uma sintaxe estática como
tstypeof Foo::barcompilasse para"string". Mesmo uma RTTI básica já poderia eliminar imediatamente muito código TypeScript boilerplate e confusoFico feliz em ver este texto. Comecei com TypeScript em 2018 e, depois de uns 2 anos, comecei a acreditar que ele, por natureza, não é uma solução completa
Vim de Haskell/C#/F#, e, mesmo com um sistema de tipos poderoso, TS não oferece muitos dos benefícios que essas linguagens dão, exceto em parte durante o desenvolvimento. Ao lidar com o mundo real, ele é muito mais limitado na prática do que C#. Se você não souber que, depois da compilação, ele cai para JS sem verificações, precisa estar sempre consciente das abstrações vazadas para evitar bugs que deveriam ser impossíveis em uma ferramenta que se diz estaticamente tipada
Se você precisa desenvolver em .NET, usa C#; se precisa desenvolver para a web, usa TypeScript. C# tem tipagem nominal e genéricos reificados bastante bons; TypeScript tem tipagem estrutural e, em troca de abrir mão da solidez, possui um sistema de tipos poderoso para expressar relações de tipos complexas.
Duvido que uma linguagem que juntasse as duas fosse boa; as duas funcionam bem em direções diferentes, mas essas direções não combinam muito entre si
Para validação de dados em tempo de execução, usei https://zod.dev e fiquei totalmente satisfeito
Achei bem legal poder expressar a intenção ali mesmo com uma API fluente, sem definir separadamente um tipo nominal distinto
Um exemplo simples: eles são necessários para diferenciar, no domínio, o número
2do valor monetário2