Eu sempre o vi mais por uma questão de conveniência, do que se um algoritmo pode ou não ser expresso. Se eu realmente quisesse executar programas como o inventado por Mitchell, escreveria o simulador de máquina de Turing apropriado na minha linguagem estaticamente tipada.
O truque com um sistema de tipo estático é oferecer os tipos certos de flexibilidade apenas para os casos em que a flexibilidade permite que você escreva códigos que sejam mais facilmente mantidos.
Aqui estão alguns exemplos de técnicas de estruturação de programas que, às vezes, são mais fáceis de gerenciar dinamicamente do que em linguagens de tipo estaticamente.
Genéricos e Containers
Em linguagens estaticamente datadas antes de ML (c. 1973) e CLU (c. 1974), não era difícil criar uma árvore de strings vermelho-preto, uma árvore de números vermelho-preto, uma árvore de números flutuantes vermelho-preto ou um vermelho-preto árvore de elementos de tipo específico Foo
. No entanto, foi difícil (talvez impossível) criar uma única implementação de uma árvore vermelho-preta que fosse estaticamente verificada e que pudesse lidar com qualquer um desses tipos de dados. As formas de contornar o problema foram (1) interromper completamente o sistema de tipos (por exemplo: usandovoid *
em C), (2) para escrever para si mesmo algum tipo de pré-processador de macro e, em seguida, escreva macros que produzem o código para cada tipo específico que você deseja ou (3) use a abordagem Lisp / Smalltalk (e Java) para verificar o tipo de extração extraída. objeto dinamicamente.
ML e CLU introduziram a noção de, respectivamente, tipos parametrizados inferidos e declarados explicitamente (estáticos), que permitem escrever tipos de contêiner genéricos, tipicamente estaticamente.
Polimorfismo do subtipo
Nas linguagens estaticamente datadas antes de Simula67 (c. 1967) e Hope (c. 1977), não era possível realizar expedições dinâmicas e verificar estaticamente se você havia coberto o caso para todos os subtipos. Muitas línguas tiveram algum tipo de uniões marcadas , mas foi a responsabilidade do programador para se certificar de que seus case
ou switch
declarações, ou as suas tabelas de salto, cobriu cada tag possível.
Os idiomas que seguem o modelo Simula (C ++, Java, C #, Eiffel) fornecem subclasses de classes abstratas, nas quais o compilador pode verificar se cada subclasse implementou todos os métodos declarados pela classe pai. Os idiomas que seguem o modelo Hope (todas as variantes de ML, de SML / NJ a Haskell) possuem subtipos algébricos, nos quais o compilador pode verificar se todas as typecase
declarações abrangem todos os subtipos.
Patches de Macacos e Programação Orientada a Aspectos
Os sistemas dinâmicos facilitam muito uma variedade de técnicas de prototipagem. Nas linguagens em que os tipos são representados por mapas de hash de strings para funções (por exemplo, Python, Javascript, Ruby), é muito fácil alterar globalmente o comportamento de cada módulo que depende de um tipo específico, simplesmente modificando dinamicamente o mapa de hash que representa esse tipo.
Embora existam maneiras óbvias pelas quais o patch para macacos pode ser usado para dificultar a manutenção de programas, também existem maneiras pelas quais ele pode realmente ser usado para "bem", em vez de "mal". Em particular, com a programação orientada a aspectos, pode-se usar técnicas semelhantes a patches de macaco para fazer coisas como modificar o tipo de arquivo para apontar para um sistema de arquivos virtualizado, permitindo a construção de infra-estruturas de teste de unidade como "gratuitas" ou modificar tipos de exceção simples para que eles imprima mensagens de log sempre que forem capturadas para melhor depuração.
Ao contrário do Generics and Subtype Polymorphism, onde as principais idéias de verificação estática estavam disponíveis na década de 1970, a verificação estática para programação orientada a aspectos é (eu acho) uma área de pesquisa ativa. Eu não sei muito sobre isso, exceto que existe uma linguagem chamada AspectJ desde 2001.