Estou usando python há alguns dias e acho que entendo a diferença entre digitação dinâmica e estática. O que não entendo é em que circunstâncias seria preferido. É flexível e legível, mas à custa de mais verificações em tempo de execução e testes de unidade adicionais necessários.
Além de critérios não funcionais, como flexibilidade e legibilidade, que motivos existem para escolher a digitação dinâmica? O que posso fazer com a digitação dinâmica que não é possível de outra maneira? Que exemplo de código específico você pode imaginar que ilustra uma vantagem concreta da digitação dinâmica?
dynamic-typing
static-typing
Justin984
fonte
fonte
Respostas:
Como você pediu um exemplo específico, eu darei um.
O ORM maciço de Rob Conery tem 400 linhas de código. É tão pequeno porque Rob é capaz de mapear tabelas SQL e fornecer resultados de objetos sem exigir muitos tipos estáticos para espelhar as tabelas SQL. Isso é feito usando o
dynamic
tipo de dados em C #. A página da Web de Rob descreve esse processo em detalhes, mas parece claro que, nesse caso de uso específico, a digitação dinâmica é em grande parte responsável pela brevidade do código.Compare com o Dapper de Sam Saffron , que usa tipos estáticos; a
SQLMapper
classe sozinha possui 3000 linhas de código.Observe que as isenções de responsabilidade usuais se aplicam e sua milhagem pode variar; Dapper tem objetivos diferentes dos de Massive. Apenas aponto isso como um exemplo de algo que você pode fazer em 400 linhas de código que provavelmente não seriam possíveis sem a digitação dinâmica.
A digitação dinâmica permite adiar suas decisões de digitação para o tempo de execução. Isso é tudo.
Se você usa uma linguagem de tipo dinâmico ou estaticamente, suas escolhas de tipo ainda devem ser sensatas. Você não adicionará duas cadeias e esperará uma resposta numérica, a menos que as cadeias contenham dados numéricos; caso contrário, obterá resultados inesperados. Um idioma digitado estaticamente não permitirá que você faça isso em primeiro lugar.
Os defensores das linguagens de tipo estaticamente apontam que o compilador pode fazer uma quantidade substancial de "verificação de integridade" do seu código em tempo de compilação, antes que uma única linha seja executada. Esta é uma coisa boa ™.
C # tem a
dynamic
palavra - chave, que permite adiar a decisão de digitar em tempo de execução sem perder os benefícios da segurança de tipo estático no restante do seu código. A inferência de tipo (var
) elimina grande parte da dor de escrever em um idioma estaticamente digitado, removendo a necessidade de sempre declarar explicitamente os tipos.Linguagens dinâmicas parecem favorecer uma abordagem mais interativa e imediata à programação. Ninguém espera que você precise escrever uma classe e passar por um ciclo de compilação para digitar um pouco do código Lisp e assisti-lo executar. No entanto, é exatamente isso que eu devo fazer em c #.
fonte
Frases como "digitação estática" e "digitação dinâmica" são muito usadas, e as pessoas tendem a usar definições sutilmente diferentes, então vamos começar esclarecendo o que queremos dizer.
Considere uma linguagem que tenha tipos estáticos que são verificados em tempo de compilação. Mas digamos que um erro de tipo gere apenas um aviso não fatal e, em tempo de execução, tudo é tipificado por pato. Esses tipos estáticos são apenas para conveniência do programador e não afetam o codegen. Isso ilustra que a digitação estática por si só não impõe limitações e não é mutuamente exclusiva na digitação dinâmica. (Objective-C é muito parecido com isso.)
Mas a maioria dos sistemas do tipo estático não se comporta dessa maneira. Há duas propriedades comuns de sistemas de tipo estático que podem impor limitações:
O compilador pode rejeitar um programa que contém um erro de tipo estático.
Essa é uma limitação, porque muitos programas seguros de tipo necessariamente contêm um erro de tipo estático.
Por exemplo, eu tenho um script Python que precisa ser executado como Python 2 e Python 3. Algumas funções alteraram seus tipos de parâmetros entre Python 2 e 3, portanto, tenho um código como este:
Um verificador de tipo estático do Python 2 rejeitaria o código do Python 3 (e vice-versa), mesmo que nunca fosse executado. Meu programa de segurança de tipo contém um erro de tipo estático.
Como outro exemplo, considere um programa Mac que deseja executar no OS X 10.6, mas aproveite os novos recursos do 10.7. Os métodos 10.7 podem ou não existir em tempo de execução, e cabe a mim, o programador, detectá-los. Um verificador de tipo estático é forçado a rejeitar meu programa para garantir a segurança do tipo ou aceitar o programa, além da possibilidade de produzir um erro de tipo (falta de função) em tempo de execução.
A verificação de tipo estático pressupõe que o ambiente de tempo de execução seja descrito adequadamente pelas informações de tempo de compilação. Mas prever o futuro é arriscado!
Aqui está mais uma limitação:
O compilador pode gerar código que assume que o tipo de tempo de execução é o tipo estático.
Assumir que os tipos estáticos estão "corretos" oferece muitas oportunidades de otimização, mas essas otimizações podem ser limitativas. Um bom exemplo são objetos proxy, por exemplo, remoting. Digamos que você deseje ter um objeto proxy local que encaminhe invocações de métodos para um objeto real em outro processo. Seria bom se o proxy fosse genérico (para que ele possa se disfarçar como qualquer objeto) e transparente (para que o código existente não precise saber que está falando com um proxy). Mas, para fazer isso, o compilador não pode gerar código que pressupõe que os tipos estáticos estejam corretos, por exemplo, através de chamadas de método com estaticamente embutido, porque isso falhará se o objeto for realmente um proxy.
Exemplos dessa comunicação remota em ação incluem o NSXPCConnection da ObjC ou o TransparentProxy do C # (cuja implementação exigiu algumas pessimizações no tempo de execução - veja aqui para uma discussão).
Quando o codegen não depende dos tipos estáticos e você possui recursos como encaminhamento de mensagens, pode fazer muitas coisas legais com objetos proxy, depuração etc.
Portanto, é uma amostra de algumas das coisas que você pode fazer se não precisar satisfazer um verificador de tipos. As limitações não são impostas por tipos estáticos, mas pela verificação de tipo estático imposta.
fonte
A Python 2 static type checker would reject the Python 3 code (and vice versa), even though it would never be executed. My type safe program contains a static type error.
Em qualquer linguagem estática razoável, você pode fazer isso com umaIFDEF
instrução de pré-processador de tipo, mantendo a segurança de tipo nos dois casos.Variáveis do tipo pato são a primeira coisa que todos pensam, mas na maioria dos casos, você pode obter os mesmos benefícios através da inferência estática de tipo.
Mas digitar pato em coleções criadas dinamicamente é difícil de se obter de qualquer outra maneira:
Então, que tipo
JSON.parse
retorna? Um dicionário de matrizes-de-inteiros-ou-dicionários-de-strings? Não, mesmo isso não é geral o suficiente.JSON.parse
precisa retornar algum tipo de "valor variante" que possa ser nulo, bool, float, string, matriz de qualquer um desses tipos recursivamente ou dicionário da string para qualquer um desses tipos recursivamente. Os principais pontos fortes da digitação dinâmica são os tipos de variantes.Até agora, esse é um benefício de tipos dinâmicos , não de linguagens dinamicamente tipadas. Uma linguagem estática decente pode simular perfeitamente qualquer tipo desse tipo. (E mesmo idiomas "ruins" geralmente podem simulá-los, quebrando a segurança do tipo sob o capô e / ou exigindo a sintaxe de acesso desajeitada.)
A vantagem de linguagens de tipo dinâmico é que esses tipos não podem ser inferidos por sistemas de inferência de tipo estático. Você precisa escrever o tipo explicitamente. Mas, em muitos casos, incluindo o código uma vez, o código para descrever o tipo é exatamente tão complicado quanto o código para analisar / construir os objetos sem descrever o tipo, de modo que ainda não é necessariamente uma vantagem.
fonte
Como todo sistema de tipo estático remotamente prático é severamente limitado em comparação com a linguagem de programação em questão, ele não pode expressar todos os invariantes que código poderia verificar em tempo de execução. Para não contornar as garantias que um sistema de tipos tenta dar, ele opta por ser conservador e não permitir casos de uso que passariam nessas verificações, mas não pode ser comprovado (no sistema de tipos).
Eu vou fazer um exemplo. Suponha que você implemente um modelo de dados simples para descrever objetos de dados, coleções deles etc., que são estaticamente digitados no sentido de que, se o modelo diz que o atributo
x
do objeto do tipo Foo possui um número inteiro, ele deve sempre conter um número inteiro. Como essa é uma construção de tempo de execução, você não pode digitá-la estaticamente. Suponha que você armazene os dados descritos nos arquivos YAML. Você cria um mapa de hash (para ser entregue a uma biblioteca YAML posteriormente), obtém ox
atributo, armazena-o no mapa, obtém o outro atributo que, por acaso, é uma string, ... espera um segundo? Qual é o tipo dethe_map[some_key]
agora? Bem, tiro, sabemos quesome_key
é'x'
e, portanto, o resultado deve ser um número inteiro, mas o sistema de tipos não pode sequer começar a raciocinar sobre isso.Alguns sistemas de tipos pesquisados ativamente podem funcionar para esse exemplo específico, mas eles são extremamente complicados (tanto para escritores de compiladores implementarem quanto para que o programador raciocine), especialmente para algo tão "simples" (quero dizer, eu apenas expliquei isso em um parágrafo).
Obviamente, a solução de hoje é encaixotar tudo e depois lançar (ou ter vários métodos substituídos, a maioria dos quais gera exceções "não implementadas"). Mas isso não é estaticamente digitado, é um hack no sistema de tipos para fazer as verificações em tempo de execução.
fonte
Não há nada que você possa fazer com a digitação dinâmica que você não possa fazer com a digitação estática, porque você pode implementar a digitação dinâmica em cima de um idioma digitado estaticamente.
Um pequeno exemplo em Haskell:
Com casos suficientes, você pode implementar qualquer sistema de tipo dinâmico.
Por outro lado, você também pode converter qualquer programa estaticamente digitado em um dinâmico equivalente. Obviamente, você perderia todas as garantias de correção em tempo de compilação fornecidas pela linguagem de tipo estaticamente.
Edit: Eu queria manter isso simples, mas aqui estão mais detalhes sobre um modelo de objeto
Uma função pega uma lista de dados como argumentos e executa cálculos com efeitos colaterais no ImplMonad e retorna um dado.
DMember
é um valor de membro ou uma função.Estenda
Data
para incluir objetos e funções. Objetos são listas de membros nomeados.Esses tipos estáticos são suficientes para implementar todos os sistemas de objetos com tipos dinâmicos com os quais estou familiarizado.
fonte
Data
.+
operador que combina doisData
valores em outroData
valor.Data
representa os valores padrão no sistema de tipos dinâmicos.Membranas :
Cada tipo é envolvido por um tipo que tem a mesma interface, mas que intercepta mensagens, quebra e quebra valores quando cruzam a membrana. Qual é o tipo da função de quebra automática em seu idioma estaticamente digitado favorito? Talvez Haskell tenha um tipo para essas funções, mas a maioria das linguagens com estaticamente não usa ou acabam usando Object → Object, abdicando efetivamente de sua responsabilidade como verificadores de tipos.
fonte
class Foo a where ...
data Wrapper = forall a. Foo a => Wrapper a
String
pois é um tipo concreto em Java. Smalltalk não tem esse problema porque não tenta digitar#doesNotUnderstand
.Como alguém mencionado, em teoria, não há muito que você possa fazer com a digitação dinâmica que você não poderia fazer com a digitação estática se implementasse determinados mecanismos por conta própria. A maioria dos idiomas fornece os mecanismos de relaxamento de tipo para oferecer suporte à flexibilidade, como ponteiros nulos e tipo de objeto raiz ou interface vazia.
A melhor pergunta é por que a digitação dinâmica é mais adequada e mais apropriada em determinadas situações e problemas.
Primeiro, vamos definir
Entidade - eu precisaria de uma noção geral de alguma entidade no código. Pode ser qualquer coisa, desde número primitivo a dados complexos.
Comportamento - digamos que nossa entidade tenha algum estado e um conjunto de métodos que permitem ao mundo exterior instruir a entidade a certas reações. Vamos chamar o estado + interface desta entidade de seu comportamento. Uma entidade pode ter mais de um comportamento combinado de uma certa maneira pelas ferramentas fornecidas pela linguagem.
Definições de entidades e seus comportamentos - toda linguagem fornece alguns meios de abstração que ajudam a definir comportamentos (conjunto de métodos + estado interno) de determinadas entidades no programa. Você pode atribuir um nome a esses comportamentos e dizer que todas as instâncias que possuem esse comportamento são de determinado tipo .
Provavelmente isso é algo que não é tão familiar. E como você disse, entendeu a diferença, mas ainda assim. Provavelmente não é uma explicação completa e precisa, mas espero que seja divertida o suficiente para trazer algum valor :)
Tipagem estática - o comportamento de todas as entidades do seu programa é examinado em tempo de compilação, antes do código ser iniciado. Isso significa que se você deseja, por exemplo, que sua entidade do tipo Person tenha um comportamento (para se comportar como) Mágico, será necessário definir a entidade MagicianPerson e fornecer comportamentos de um mago como throwMagic (). Se você no seu código, por engano, informe para o compilador Person.throwMagic () comum, informará
"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".
Digitação dinâmica - em ambientes de digitação dinâmica, os comportamentos disponíveis das entidades não são verificados até que você realmente tente fazer algo com determinada entidade. A execução de código Ruby que solicita a Person.throwMagic () não será capturada até que seu código realmente chegue lá. Isso parece frustrante, não é? Mas parece revelador também. Com base nessa propriedade, você pode fazer coisas interessantes. Por exemplo, digamos que você crie um jogo em que qualquer coisa possa se voltar para o Magician e você realmente não sabe quem será, até chegar ao ponto certo do código. E então Sapo vem e você diz
HeyYouConcreteInstanceOfFrog.include Magic
e a partir de então este sapo se torna um sapo em particular que possui poderes mágicos. Outros sapos, ainda não. Veja bem, nas linguagens estáticas de digitação, você teria que definir essa relação por alguma média padrão de combinação de comportamentos (como implementação de interface). Na linguagem de digitação dinâmica, você pode fazer isso em tempo de execução e ninguém se importará.A maioria das linguagens de digitação dinâmica possui mecanismos para fornecer um comportamento genérico que captura qualquer mensagem passada para sua interface. Por exemplo, Ruby
method_missing
e PHP,__call
se bem me lembro. Isso significa que você pode fazer qualquer tipo de coisa interessante no tempo de execução do programa e tomar uma decisão de tipo com base no estado atual do programa. Isso traz ferramentas para modelagem de um problema que são muito mais flexíveis do que em, digamos, linguagem de programação estática conservadora como Java.fonte