2 pontos por GN⁺ 2026-06-12 | 1 comentários | Compartilhar no WhatsApp
  • A razão pela qual a popularidade da tipagem estática caiu dos anos 2000 até o início dos anos 2010 e voltou a subir em meados/fim dos anos 2010 foi a melhora na qualidade dos sistemas de tipos estáticos, não uma moda
  • Ao cavar um buraco, assim como é melhor usar as mãos do que uma pá de papel, um sistema de tipos dinâmico é melhor do que um sistema de tipos estáticos ruim
  • Sistemas de tipos estáticos antigos, como o Java inicial ou o C++98, nem sequer distinguiam nullable de non-nullable, não tinham tipos soma e exigiam escrever nomes de tipos manualmente o tempo todo
  • Sistemas de tipos modernos, como TypeScript, Haskell, Swift e Rust, oferecem por padrão distinção de null, tipos soma/union e inferência de tipos
  • Com a popularização do autocompletar de métodos nas IDEs, a informação inserida na tipagem estática passou a trazer ganhos de produtividade além da checagem de erros

Hipótese sobre a mudança de popularidade da tipagem estática

  • O fenômeno de a tipagem estática ter perdido popularidade nos anos 2000~início dos anos 2010 e voltado a crescer em meados/fim dos anos 2010
    não aconteceu porque programação é uma indústria guiada por modas, mas porque a qualidade dos sistemas de tipos estáticos amplamente usados melhorou

A analogia da pá — a qualidade da ferramenta determina a escolha

  • Ao cavar um buraco, se a pá for útil, naturalmente você a usará em vez das mãos;
    mas, se a única pá disponível for feita de papel, ela só vai remexer a terra sem sentido, então é melhor usar as mãos
  • Em um sistema de tipos dinâmico, é preciso rastrear inteiramente na própria cabeça o estado e o conteúdo de variáveis e campos;
    como o computador nem ajuda nem atrapalha, isso equivale a cavar com as mãos
  • Os sistemas de tipos estáticos fracos, comuns nos anos 1990 e início dos anos 2000, eram como uma pá de papel
    • Não conseguiam ajudar nem mesmo com algo simples como distinguir ponteiros nullable e non-nullable
    • Tinham apenas tipos produto (product type) e não tinham tipos soma (sum type)
    • Havia o ônus de ter de escrever nomes de tipos manualmente em toda parte
    • Código como BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); é um pequeno desastre

O que os sistemas de tipos modernos sempre oferecem

  • Em contraste com sistemas de tipos modernos como TypeScript, Haskell, MyPy, Swift e Rust, eles sempre oferecem o seguinte
  • Distinção entre nullable e non-nullable

    • Haskell oferece Maybe t, TypeScript T | null, Swift T? e Rust Optional<T>
    • O sistema de tipos informa com facilidade todos os pontos em que é necessário checar null e onde isso foi omitido
    • Na prática, quase não se vê erros de ponteiro nulo em runtime
  • Tipos soma ou union types

    • Isso viabiliza a prática de "tornar estados inválidos impossíveis de representar (Make invalid states unrepresentable)"
    • É possível criar objetos de máquina de estados (state machine) com vários campos, e cada campo só existe quando o sistema está no estado relevante
  • Inferência de tipos

    • Como o compilador pode entender let x = 5; como um número, não é necessário escrever let x: number = 5;

O ganho adicional trazido pela disseminação de recursos de IDE

  • A popularização de recursos de IDE como autocompletar nomes de métodos aumentou a utilidade dos sistemas de tipos estáticos
    • Nos anos 1990, o Intellisense do Visual Studio era um recurso matador, mas nos anos 2020 quase toda IDE ou editor oferece algo semelhante
    • A informação colocada no sistema de tipos estáticos gera ganhos adicionais de produtividade, além da checagem de erros

Conclusão

  • Um bom sistema de tipos dinâmico é melhor do que um sistema de tipos estáticos ruim
  • Ainda assim, agora temos sistemas de tipos estáticos muito melhores do que no passado

1 comentários

 
GN⁺ 2026-06-12
Opiniões no Lobste.rs
  • O texto é bom, mas não concordo completamente. Mesmo que os sistemas de tipos estáticos do começo dos anos 2000 não fossem excelentes, ainda eram muito melhores do que não ter tipagem estática nenhuma
    Não havia tipos soma fechados, mas muita coisa podia ser modelada com subtipos; não havia tipos não anuláveis, mas as referências e tipos sem ponteiro de C++, além dos tipos primitivos de Java, cobriam parte disso. Em Ruby ou JavaScript, todos os tipos não só podiam ser nulos, como também podiam ser tratados como string, inteiro ou qualquer outro tipo do programa, o que era uma situação pior
    Acho que uma grande razão para a mudança de rumo em relação à tipagem estática foi que, no boom das redes sociais da Web 2.0, a vantagem de sair na frente era mais importante do que qualquer outra coisa. Mesmo acumulando dívida técnica com Ruby ou Python, valia mais a pena lançar rápido e iterar do que ser deixado para trás como o Friendster ou o Digg, e, se estivesse lento, dava para simplesmente comprar mais servidores com o capital barato de juros baixos que era fácil conseguir naquela época
    Depois, no boom mobile, o software passou a rodar em dispositivos de usuários limitados e fora de controle, e aplicativos lentos com tipagem dinâmica eram simplesmente lentos na prática, enquanto erros de tipo também não podiam ser recuperados elegantemente com um handler de resposta no topo, como em um servidor. Nesse ambiente, a segurança e o desempenho da tipagem estática ficaram muito mais convincentes

    • Há vários artigos comparando Java e C++ no estilo dos anos 90 com codebases em linguagens de tipagem dinâmica e concluindo que a taxa de bugs é parecida, e os defensores de linguagens dinâmicas costumam usar isso como argumento de que tipagem estática não é útil
      No começo dos anos 2000 eu também concordava, porque os sistemas de tipos da época geralmente só forçavam propriedades que quase nunca estavam erradas, enquanto impunham restrições que não ajudavam a estruturar o código. Em especial, a forma como subtipagem e herança de implementação vinham acopladas não era flexível
      Minha visão mudou ao usar sistemas de tipos mais modernos. No snmalloc, o sistema de tipos de C++ impõe uma máquina de estados de posse de memória, e em outros codebases verifica o comportamento correto de overflow em contadores de ring buffer. Nos dois casos, se desse errado, seria chato de depurar e uma fonte comum de erro, mas o compilador realmente falhou em código que eu achava que estava certo, impedindo que o bug entrasse na árvore
    • Acho que desenvolver em linguagens de tipagem dinâmica é mais lento do que em linguagens de tipagem estática. Vejo essa afirmação contrária o tempo todo, mas não entendo
      No IDE, apertar . e digitar um pouco do nome do método até dar Enter na sugestão certa economiza 2 segundos a cada poucos segundos, e também economiza aqueles 30 segundos de ter de procurar a definição da classe quando você não sabe quais métodos existem. Esse princípio também aparece muito bem em https://grugbrain.dev/#grug-on-type-systems
      Como escrevemos linhas que chamam métodos com muito mais frequência do que escrevemos os tipos dos parâmetros das funções, a troca pesa de forma esmagadora contra a tipagem dinâmica. O que realmente tinha valor não era permitir código absurdo que só vai falhar em runtime, e sim omitir o tipo de variáveis locais, e linguagens estáticas nunca precisaram proibir isso em primeiro lugar
    • Os sistemas de tipos populares do começo dos anos 2000 não eram apenas “não tão incríveis”; eles eram ruins e muito verbosos
      Nos raros codebases que usavam o sistema de tipos a sério, acumulavam-se páginas e páginas de código que não diziam nada, e ainda assim havia montanhas de condicionais em runtime; além disso, em Java, à medida que a hierarquia de tipos crescia, o programa também ficava efetivamente mais lento. A maioria dos codebases usava tipos de forma esparsa e colocava muitas condicionais em runtime, sem grande economia na cobertura de testes necessária em comparação com um sistema de tipagem dinâmica
      Linguagens dinâmicas não ofereciam recompensas estáticas, mas eram concisas, fáceis de ler e revisar, e também fáceis de testar. Isso era especialmente verdade em ambientes como os frameworks de injeção de dependência do fim dos anos 90 e começo dos 2000, em que adicionar um novo serviço exigia editar vários arquivos XML. Também dava para trabalhar sem um IDE consumindo metade da RAM
      Como meu início de carreira foi exatamente assim, concordo bastante com o texto. A relação custo-benefício de Java 1.4 até Java 6 era tão ruim que quase me fez desistir de linguagens estaticamente tipadas, e só anos depois, mexendo com Haskell por hobby, percebi que tipagem estática podia sim ter uma relação custo-benefício razoável e que o problema era o Java. O ensaio “python is not java” também mostra bem aquele período sombrio
    • A subtipagem baseada em herança era ainda mais limitada. Você não tinha a usabilidade de pattern matching com verificação de exaustividade, e a implementação ficava espalhada em vários lugares
    • A explicação de que era importante colocar um site no ar antes dos concorrentes, colocá-lo diante dos usuários e travar as economias de escala soa bastante familiar com a situação atual também
  • Tenho dúvidas se realmente vimos, na prática, o ganho de confiabilidade do nosso software depois que tipagem estática virou o espírito do tempo
    Sempre achei que as vantagens da tipagem estática estavam muito mais no feedback imediato durante o desenvolvimento e na redução de falhas fatais em runtime; em teoria essas falhas sempre podem acontecer, mas na prática não parecia que ocorriam com tanta frequência

    • Vi, sim. Quando passamos a ter como meta zero erros de TypeScript em uma base de código TypeScript não trivial, as tentativas de chamar métodos em undefined e null caíram drasticamente
      Alguns juniores e até alguns seniores eram céticos no começo e achavam que isso só ia espalhar @ts-ignore por todo lado, mas na prática surgiram só uns três casos, incluindo os causados por tipos quebrados em dependências. Antes, o app quebrava mais ou menos uma vez por semana na branch de desenvolvimento por confusão de tipos e travava meu trabalho; hoje nem lembro quando foi a última vez que isso aconteceu
      Só de satisfazer o tsc, já diminuem os bugs relacionados a tipos, inclusive quando eu mesmo não escrevi o código. Em contrapartida, os linters hoje em dia são zelosos demais, e ao tentar satisfazer ferramentas como o Sonar já vi refatorações quebrarem de verdade. 95% dos avisos eram falsos, 3% eram bugs da própria ferramenta, e os 2% que ajudaram também não eram a causa real de bugs. Em vez de gastar uma semana ajustando a base de código para pegar um bug, acabei introduzindo mais dois no processo
      O trabalho de satisfazer o tsc gerava mais ou menos 2 correções de bug reais por dia e 1 regressão, mas a regressão em geral tinha gravidade menor, mais como comportamento incorreto do que um crash completo
      Se você adicionar testes baseados em propriedades a isso, levava em média de 2 a 4 horas e sempre revelava pelo menos um bug. Se o código permite testes baseados em propriedades, você deveria fazer isso
      Usando o modelo barato DeepSeek V4 Flash para ampliar a cobertura de testes, tomando cuidado para não criar testes lixo, eu corrigia uns 2 ou 3 bugs lógicos por dia, sem crashes. Mas o conjunto de testes mal é sustentável em termos de manutenção
      Quando deixei um júnior criar testes de qualquer jeito com modelos da linha Sonnet e Opus 4.5 e 4.6, os modelos só produziam testes que “documentavam o comportamento atual”, então o efeito das correções era pequeno, e o conjunto de testes ficou impossível de manter, então tivemos de descartá-lo
      Testes baseados em modelos são muito bons para encontrar bugs, mas a configuração é complexa, e é bem trabalhoso induzi-los a explorar os cantos do sistema sem desperdiçar ciclos em funcionalidade superficial. Algo como um fuzzer baseado em modelos com perfil parece interessante
      Resumindo: verificadores de tipo são bons em capturar falhas fatais e vários tipos de confusão, e testes baseados em propriedades são excelentes. Testes comuns exigem muita disciplina para gerar retorno consistente
    • Pessoalmente, eu diria que sim. No JavaScript que eu uso, bugs de ponteiro nulo se tornaram quase desprezíveis depois da migração para TypeScript, e com meus colegas foi parecido
  • O ponto com que mais discordo aqui é juntar TypeScript com um bom sistema de tipos

    • Exato. TypeScript não é sound, e a forma como ele faz narrowing de tipos através de await já me pegou várias vezes. Ainda assim, é fato que melhorou a situação de forma dramática
      Sinceramente, até tipagem estrutural eu acabei aceitando, e acho que isso vai influenciar positivamente o design de linguagens no futuro
  • Esse argumento é pouco convincente. Linguagens de programação decentes com tipos algébricos de dados e inferência de tipos já existiam desde meados dos anos 90
    Os sistemas de tipos de Java e C++ eram muito pobres, mas SML, OCaml e Haskell já existiam e a sensação era bem parecida com a de hoje. Se as pessoas não usavam essas linguagens, isso é uma questão de cultura, adoção e requisitos não verbalizados, e não algo que se explique apenas por “os sistemas de tipos utilizáveis não eram bons o bastante”
    Ou, se a tese for “os sistemas de tipos das linguagens populares da época eram ruins, e os das linguagens populares de hoje são melhores, então sistemas de tipos ficaram mais populares”, isso soa como raciocínio circular
    Também há muita nuance na diferença entre linguagens projetadas junto com um sistema de tipos e linguagens originalmente projetadas sem tipos, às quais um sistema de tipos foi acoplado depois

  • Mesmo vindo de uma posição que historicamente preferia tipagem dinâmica, acho este texto bastante justo. Hoje trabalho com C#, uso Lisp por hobby e antes também usei Python
    Quando eu tinha de usar Java 5, em geral ficava brigando com o sistema de tipos por causa de decisões ruins dos autores de bibliotecas. Depois que migrei para C# por volta de 2010, o sistema de tipos deixou de ser ativamente prejudicial, mas no geral era redundante e ainda não evitava a confusão de tipos mais comum em Python, a exceção de ponteiro nulo
    O sistema de tipos de C# só começou a me ajudar de verdade por volta de 2020, quando entraram os tipos de referência não anuláveis. Este ano também entram tipos união nativos, mas bibliotecas de tipos união que forçam exaustividade já eram possíveis pelo menos desde 2016, e eu comecei a usá-las em 2020
    Ainda acho que moda continua tendo um papel, mas parte disso não é ruim. Linguagens da moda com sistemas de tipos mais expressivos também trouxeram melhorias para as linguagens comuns que usamos para ganhar dinheiro

  • Haskell e seu sistema de tipos já existiam nos anos 2000. Não eram tão amplamente usados quanto hoje, mas certamente existiam, então essa afirmação precisa ser ajustada nesse ponto.
    Pessoalmente, acho que o TypeScript foi um grande fator para familiarizar usuários de linguagens mainstream com sistemas de tipos melhores. Além da qualidade e do apoio da Microsoft, ele tinha a vantagem de se aplicar ao JavaScript, e o JavaScript precisava de tipos mais urgentemente do que o Python. Por causa de “Undefined is not a function.” e de “The good parts.”

    • Seria bom se saísse um livro de “good parts” alinhado com as versões mais recentes do JavaScript, mantendo a concisão.
      “Real World Haskell” saiu em 2008 e tinha como objetivo fazer o Haskell parecer mais atraente para programadores mainstream. Não sei o quanto isso ajudou a espalhar a boa nova.
      No mundo Java, o Scala trouxe tipos interessantes em 2004, e no .NET o F# apareceu em 2005. O Scala talvez tenha conseguido os usuários de destaque mais visíveis, como o Twitter, mas não estava numa posição de absorver uma grande parcela dos usuários daquela plataforma como o TypeScript, nem era atraente o bastante para atrair em massa usuários de outras linguagens como Rust ou Go
    • O texto já aborda essa questão. Ele compara sistemas de tipos estáticos fracos como os do Java inicial ou do C++98, populares nos anos 90 e começo dos anos 2000, a pás de papel
      No parágrafo seguinte, ele menciona Haskell como um “sistema de tipos moderno”, mas a porcentagem de pessoas com experiência em Haskell no fim dos anos 90 e começo dos anos 2000, incluindo quem só mexeu por conta própria, era na prática próxima de 0%. O texto está falando de como a maioria dos desenvolvedores daquela época experimentava linguagens com tipagem estática e por que essa maioria evitava coletivamente linguagens desse tipo
    • Acho que Haskell e OCaml sofrem até certo ponto por terem um ecossistema de ferramentas fraco. A linguagem em si é excelente, mas acabam perdendo adoção por causa de inúmeras pequenas dores de cabeça nas ferramentas
      Por exemplo, para usar dune em OCaml, você precisa entender arquivos opam, arquivos dune, a sintaxe de ocaml module e a sintaxe de ocaml. As extensões opcionais de compilador do Haskell passam a mesma sensação intimidadora
      Em contraste com o cargo, em que basta conhecer toml e Rust