Primeiro, quero dizer que Java é a única linguagem que já usei, então, desculpe minha ignorância sobre esse assunto.
Idiomas dinamicamente digitados permitem que você coloque qualquer valor em qualquer variável. Então, por exemplo, você pode escrever a seguinte função (psuedocode):
void makeItBark(dog){
dog.bark();
}
E você pode passar dentro dele qualquer valor. Enquanto o valor tiver um bark()
método, o código será executado. Caso contrário, uma exceção de tempo de execução ou algo semelhante será lançada. (Corrija-me se estiver errado sobre isso).
Aparentemente, isso oferece flexibilidade.
No entanto, eu li algumas linguagens dinâmicas, e o que as pessoas dizem é que, ao projetar ou escrever código em uma linguagem dinâmica, você pensa sobre os tipos e os leva em consideração, tanto quanto faria em uma linguagem de tipo estaticamente.
Por exemplo, ao escrever a makeItBark()
função, você pretende que ela aceite apenas "coisas que podem latir" e ainda precisa garantir que apenas transmita esses tipos de coisas para ela. A única diferença é que agora o compilador não informa quando você cometeu um erro.
Certamente, existe uma vantagem nessa abordagem: nas linguagens estáticas, para atingir a função 'essa função aceita qualquer coisa que possa latir', é necessário implementar uma Barker
interface explícita . Ainda assim, isso parece uma pequena vantagem.
Estou esquecendo de algo? O que estou realmente ganhando usando uma linguagem de tipo dinâmico?
fonte
makeItBark(collections.namedtuple("Dog", "bark")(lambda x: "woof woof"))
. Esse argumento não é nem uma classe , é uma tupla anônima chamada. A digitação de pato ("se quack como um ...") permite fazer interfaces ad hoc com restrições praticamente nulas e sem sobrecarga sintática. Você pode fazer isso em uma linguagem como Java, mas acaba com muita reflexão confusa. Se uma função em Java requer um ArrayList e você deseja atribuir outro tipo de coleção, você é SOL. Em python, isso nem pode surgir.bark()
método, com o compilador reclamando quando você passa algo errado, mas sem ter que declarar realmente uma interface que contém bark ().getMember
função personalizada ,makeItBark
explode porque você chamou emdog.bark
vez dedog.getMember("bark")
. O que faz o código funcionar é que todos implicitamente concordam em usar o tipo de objeto nativo do Python.Just because I wrote makeItBark with my own types in mind doesn't mean you can't use yours, wheras in a static language it probably /does/ mean that.
Como apontado na minha resposta, este não é o caso em geral . Esse é o caso de Java e C #, mas essas linguagens danificaram os sistemas de tipo e módulo, para que não representem o que a digitação estática pode fazer. Eu posso escrever um perfeitamente genéricomakeItBark
em várias línguas estaticamente tipado, mesmo os não-funcionais, como C ++ ou D.Respostas:
Idiomas dinamicamente digitados são uni-tipados
Comparando sistemas de tipos , não há vantagem na digitação dinâmica. A digitação dinâmica é um caso especial de digitação estática - é uma linguagem com estaticamente onde cada variável tem o mesmo tipo. Você pode conseguir o mesmo em Java (menos concisão), tornando todas as variáveis do tipo
Object
e tendo os valores de "objeto" do tipoMap<String, Object>
:Portanto, mesmo sem reflexão, você pode obter o mesmo efeito em praticamente qualquer linguagem de tipo estaticamente, deixando de lado a conveniência sintática. Você não está recebendo nenhum poder expressivo adicional; ao contrário, você tem menos poder expressivo porque em uma linguagem de tipagem dinâmica, você está negado a capacidade de restringir variáveis para determinados tipos.
Fazendo um latido de pato em um idioma estaticamente tipado
Além disso, uma boa linguagem de tipo estaticamente permitirá que você escreva um código que funcione com qualquer tipo que tenha uma
bark
operação. No Haskell, esta é uma classe de tipo:Isso expressa a restrição de que, para que algum tipo
a
seja considerado Barkable, deve existir umabark
função que aceite um valor desse tipo e não retorne nada.Você pode escrever funções genéricas em termos de
Barkable
restrição:Isso diz que
makeItBark
funcionará para qualquer tipo que satisfaçaBarkable
os requisitos. Isso pode parecer semelhante aointerface
Java ou C #, mas tem uma grande vantagem - os tipos não precisam especificar antecipadamente quais classes de tipos eles satisfazem. Posso dizer que o tipoDuck
éBarkable
a qualquer momento, mesmo queDuck
seja um tipo de terceiros que não escrevi. De fato, não importa que o escritor deDuck
não tenha escrito umabark
função - eu posso fornecê-la posteriormente quando digo a linguagem queDuck
satisfazBarkable
:Isso diz que
Duck
s pode latir, e sua função de latido é implementada perfurando o pato antes de torná-lo charlatão. Com isso fora do caminho, podemos chamar osmakeItBark
patos.Standard ML
eOCaml
são ainda mais flexíveis, pois você pode satisfazer a mesma classe de tipo de mais de uma maneira. Nessas línguas, posso dizer que números inteiros podem ser ordenados usando a ordenação convencional e, em seguida, dar a volta e dizer que também podem ser ordenados por divisibilidade (por exemplo,10 > 5
porque 10 é divisível por 5). No Haskell, você só pode instanciar uma classe de tipo uma vez. (Isso permite que Haskell saiba automaticamente que não há problema em chamarbark
um pato; no SML ou no OCaml, você precisa ser explícito sobre qualbark
função deseja, porque pode haver mais de um.)Concisão
Claro, existem diferenças sintáticas. O código Python que você apresentou é muito mais conciso do que o equivalente em Java que escrevi. Na prática, essa concisão é uma grande parte do fascínio das linguagens de tipo dinâmico. Mas a inferência de tipo permite que você escreva um código tão conciso nas linguagens de tipo estaticamente, dispensando a necessidade de escrever explicitamente os tipos de todas as variáveis. Uma linguagem de tipo estático também pode fornecer suporte nativo para digitação dinâmica, removendo a verbosidade de todas as manipulações de elenco e mapa (por exemplo, C # 's
dynamic
).Programas corretos, mas incorretos
Para ser justo, a digitação estática exclui necessariamente alguns programas tecnicamente corretos, embora o verificador de tipos não possa verificá-lo. Por exemplo:
A maioria das linguagens de tipo estaticamente rejeitaria essa
if
declaração, mesmo que o ramo else nunca ocorra. Na prática, parece que ninguém faz uso desse tipo de código - algo muito inteligente para o verificador de tipos provavelmente fará com que futuros mantenedores do seu código amaldiçoem você e seus parentes mais próximos. Caso em questão, alguém traduziu com êxito 4 projetos Python de código aberto para Haskell, o que significa que eles não estavam fazendo nada que uma boa linguagem de tipo estatístico não pudesse compilar. Além disso, o compilador encontrou alguns bugs relacionados a tipos que os testes de unidade não estavam detectando.O argumento mais forte que eu já vi para a digitação dinâmica são as macros do Lisp, pois elas permitem que você estenda arbitrariamente a sintaxe da linguagem. No entanto, o Typed Racket é um dialeto estatístico do Lisp que possui macros, portanto parece que a digitação estática e as macros não são mutuamente exclusivas, embora talvez seja mais difícil de implementar simultaneamente.
Maçãs e laranjas
Por fim, não esqueça que há diferenças maiores nos idiomas do que apenas no sistema de tipos. Antes do Java 8, fazer qualquer tipo de programação funcional em Java era praticamente impossível; um lambda simples exigiria 4 linhas de código de classe anônimo padrão. Java também não tem suporte para literais de coleção (por exemplo
[1, 2, 3]
). Também pode haver diferenças na qualidade e disponibilidade de ferramentas (IDEs, depuradores), bibliotecas e suporte da comunidade. Quando alguém afirma ser mais produtivo em Python ou Ruby do que Java, essa disparidade de recurso precisa ser levada em consideração. Há uma diferença entre comparar idiomas com todas as baterias incluídas , núcleos de idiomas e sistemas de tipos .fonte
Esta é uma questão difícil e bastante subjetiva. (E sua pergunta pode ser encerrada com base em opiniões, mas isso não significa que é uma pergunta ruim - pelo contrário, até mesmo pensar em tais questões de meta-linguagem é um bom sinal - apenas não é adequado ao formato de perguntas e respostas deste fórum.)
Aqui está minha visão: O objetivo das linguagens de alto nível é restringir o que um programador pode fazer com o computador. Isso é surpreendente para muitas pessoas, pois elas acreditam que o objetivo é dar aos usuários mais poder e alcançar mais . Mas como tudo o que você escreve no Prolog, C ++ ou List é executado como código de máquina, é realmente impossível dar ao programador mais poder do que a linguagem assembly já fornece.
O objetivo de uma linguagem de alto nível é ajudar o programador a entender melhor o código que eles mesmos criaram e torná-los mais eficientes ao fazer a mesma coisa. Um nome de sub-rotina é mais fácil de lembrar do que um endereço hexadecimal. Um contador automático de argumentos é mais fácil de usar do que uma sequência de chamadas. Aqui, você precisa obter o número de argumentos exatamente por conta própria, sem ajuda. Um sistema de tipos vai além e restringe o tipo de argumento que você pode fornecer em um determinado local.
Aqui é onde a percepção das pessoas difere. Algumas pessoas (eu estou entre elas) acham que, desde que sua rotina de verificação de senha espere exatamente dois argumentos de qualquer maneira, e sempre uma sequência seguida por um ID numérico, é útil declarar isso no código e ser lembrado automaticamente se mais tarde você esquece de seguir essa regra. A terceirização de contabilidade de pequena escala para o compilador ajuda a liberar sua mente para preocupações de nível superior e o aprimora no design e na arquitetura do sistema. Portanto, os sistemas de tipos são uma vitória: eles deixam o computador fazer o que é bom e os humanos fazem o que são bons.
Outros vêem de maneira bem diferente. Eles não gostam de ser instruídos por um compilador sobre o que fazer. Eles não gostam do esforço extra inicial de decidir sobre a declaração de tipo e digitá-la. Eles preferem um estilo de programação exploratório em que você escreve o código comercial real sem ter um plano que indique exatamente quais tipos e argumentos usar onde. E para o estilo de programação que eles usam, isso pode ser verdade.
Estou simplificando demais aqui, é claro. A verificação de tipo não está estritamente vinculada a declarações de tipo explícitas; também há inferência de tipo. Programar com rotinas que realmente aceitam argumentos de tipos variados permite coisas bem diferentes e muito poderosas que, de outra forma, seriam impossíveis, é que muitas pessoas não são atenciosas e consistentes o suficiente para usar essa margem de manobra com sucesso.
No final, o fato de que essas linguagens diferentes são muito populares e não mostram sinais de morrer mostra que as pessoas fazem programação de maneira muito diferente. Penso que os recursos da linguagem de programação tratam principalmente de fatores humanos - o que suporta melhor o processo de tomada de decisão humana - e, desde que as pessoas trabalhem de maneira muito diferente, o mercado fornecerá soluções muito diferentes simultaneamente.
fonte
O código gravado usando linguagens dinâmicas não está acoplado a um sistema de tipo estático. Portanto, essa falta de acoplamento é uma vantagem em comparação com sistemas estáticos ruins / inadequados (embora possa ser uma lavagem ou uma desvantagem em comparação com um ótimo sistema estático).
Além disso, para uma linguagem dinâmica, um sistema de tipo estático não precisa ser projetado, implementado, testado e mantido. Isso poderia simplificar a implementação em comparação com uma linguagem com um sistema de tipo estático.
fonte