Existe uma vantagem real para linguagens dinâmicas? [fechadas]

29

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 Barkerinterface 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?

Aviv Cohn
fonte
6
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.
Phoshi
2
Esse tipo de pergunta já foi feito antes: aqui , aqui e aqui . Especificamente, o primeiro exemplo parece responder sua pergunta. Talvez você possa reformular o seu para torná-lo distinto?
logc
3
Observe que, por exemplo, em C ++, você pode ter uma função de modelo que funciona com qualquer tipo T que possua um bark()método, com o compilador reclamando quando você passa algo errado, mas sem ter que declarar realmente uma interface que contém bark ().
Wilbert
2
@Phoshi O argumento em Python ainda precisa ser de um tipo específico - por exemplo, não pode ser um número. Se você tiver sua própria implementação ad-hoc de objetos, que recupera seus membros por meio de alguma getMemberfunção personalizada , makeItBarkexplode porque você chamou em dog.barkvez de dog.getMember("bark"). O que faz o código funcionar é que todos implicitamente concordam em usar o tipo de objeto nativo do Python.
Doval
2
@ Phoshi 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érico makeItBarkem várias línguas estaticamente tipado, mesmo os não-funcionais, como C ++ ou D.
Doval

Respostas:

35

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 Objecte tendo os valores de "objeto" do tipo Map<String, Object>:

void makeItBark(Object dog) {
    Map<String, Object> dogMap = (Map<String, Object>) dog;
    Runnable bark = (Runnable) dogMap.get("bark");
    bark.run();
}

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 barkoperação. No Haskell, esta é uma classe de tipo:

class Barkable a where
    bark :: a -> unit

Isso expressa a restrição de que, para que algum tipo aseja considerado Barkable, deve existir uma barkfunção que aceite um valor desse tipo e não retorne nada.

Você pode escrever funções genéricas em termos de Barkablerestrição:

makeItBark :: Barkable a => a -> unit
makeItBark barker = bark (barker)

Isso diz que makeItBarkfuncionará para qualquer tipo que satisfaça Barkableos requisitos. Isso pode parecer semelhante ao interfaceJava ou C #, mas tem uma grande vantagem - os tipos não precisam especificar antecipadamente quais classes de tipos eles satisfazem. Posso dizer que o tipo Ducké Barkablea qualquer momento, mesmo que Duckseja um tipo de terceiros que não escrevi. De fato, não importa que o escritor de Ducknão tenha escrito uma barkfunção - eu posso fornecê-la posteriormente quando digo a linguagem que Ducksatisfaz Barkable:

instance Barkable Duck where
    bark d = quack (punch (d))

makeItBark (aDuck)

Isso diz que Ducks 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 os makeItBarkpatos.

Standard MLe OCamlsã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 > 5porque 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 chamar barkum pato; no SML ou no OCaml, você precisa ser explícito sobre qual bark 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:

if this_variable_is_always_true:
    return "some string"
else:
    return 6

A maioria das linguagens de tipo estaticamente rejeitaria essa ifdeclaraçã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 .

Doval
fonte
2
Você esqueceu de atribuir sua fonte ao primeiro parágrafo - existentialtype.wordpress.com/2011/03/19/…
2
@ Matt Re: 1, não presumi que não fosse importante; Eu o abordei sob Concisão. Re: (Pontuação: 2), embora eu nunca tenha dito isso explicitamente, por "bom" quero dizer "possui inferência completa de tipos" e "possui um sistema de módulos que permite combinar o código com as assinaturas após o fato ", e não como o Java / Interfaces de c #. Re 3, o ônus da prova é você explicar-me como, dadas duas linguagens com sintaxe e recursos equivalentes, uma digitada dinamicamente e outra com inferência total de tipo, você não seria capaz de escrever código de tamanho igual em ambas .
Doval
3
@MattFenwick Eu já justifiquei - com dois idiomas com os mesmos recursos, um com dinamismo e outro com estaticamente, a principal diferença entre eles será a presença de anotações de tipo e a inferência de tipo tira isso. Quaisquer outras diferenças na sintaxe são superficiais, e quaisquer diferenças nos recursos transformam a comparação em maçãs versus laranjas. Cabe a você mostrar como essa lógica está errada.
Doval
1
Você deveria dar uma olhada no Boo. É digitado estaticamente com inferência de tipo e possui macros que permitem que a sintaxe do idioma seja estendida.
Mason Wheeler
1
@Doval: Verdadeiro. BTW, a notação lambda não é usada exclusivamente em programação funcional: até onde eu sei, o Smalltalk possui blocos anônimos e o Smalltalk é o mais orientado a objetos possível. Portanto, geralmente a solução é passar um bloco de código anônimo com alguns parâmetros, não importa se é uma função anônima ou um objeto anônimo com exatamente um método anônimo. Eu acho que esses dois construtos expressam essencialmente a mesma idéia de duas perspectivas diferentes (a funcional e a orientada a objetos).
Giorgio
11

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.

Kilian Foth
fonte
3
Obrigado pela resposta. Você disse que algumas pessoas não gostam de ser instruídas por um compilador sobre o que fazer. [..] 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 '. É isso que eu não entendo: programar não é como improvisação musical. Na música, se você tocar uma nota errada, pode parecer legal. Na programação, se você passar algo para uma função que não deveria estar lá, provavelmente terá bugs desagradáveis. (continuando no próximo comentário).
Aviv Cohn
3
Eu concordo, mas muitas pessoas não concordam. E as pessoas são bastante possessivas com seus preconceitos mentais, principalmente porque muitas vezes não as conhecem. É por isso que debates sobre estilo de programação geralmente degeneram em discussões ou brigas, e raramente é útil iniciá-los com estranhos aleatórios na internet.
Kilian Foth
1
É por isso que - a julgar pelo que eu leio - as pessoas que usam linguagens dinâmicas levam em consideração os tipos tanto quanto as pessoas que usam linguagens estáticas. Porque quando você escreve uma função, ela deve receber argumentos de um tipo específico. Não importa se o compilador aplica isso ou não. Portanto, tudo se resume à digitação estática, ajudando você com isso, e a digitação dinâmica não. Nos dois casos, uma função precisa receber um tipo específico de entrada. Portanto, não vejo qual é a vantagem da digitação dinâmica. Mesmo se você preferir um 'estilo de programação exploratório', ainda não poderá passar o que quiser para uma função.
Aviv Cohn
1
As pessoas costumam falar sobre tipos muito diferentes de projetos (especialmente em relação ao tamanho). A lógica de negócios de um site será muito simples em comparação com um sistema ERP completo. Há menos risco de você errar e a vantagem de poder reutilizar muito simplesmente algum código é mais relevante. Digamos que eu tenha algum código que gere um PDF (ou algum HTML) de uma estrutura de dados. Agora eu tenho uma fonte de dados diferente (a primeira foi JSON de alguma API REST, agora é importadora do Excel). Em uma linguagem como Ruby, pode ser super fácil "simular" a primeira estrutura, "fazer latir" e reutilizar o código Pdf.
224153 Thorith Müller
@Prog: A verdadeira vantagem das linguagens dinâmicas é quando se trata de descrever coisas realmente difíceis com um sistema de tipos estáticos. Uma função em python, por exemplo, pode ser uma referência de função, um lambda, um objeto de função ou Deus sabe o que e tudo funcionará da mesma maneira. Você pode criar um objeto que envolve outro objeto e despacha automaticamente métodos com zero sobrecarga sintática, e todas as funções essencialmente magicamente têm tipos parametrizados. Linguagens dinâmicas são incríveis para fazer rapidamente as coisas.
Phoshi
5

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
2
As pessoas não tendem a reimplementar um sistema básico de tipo estático com seus testes de unidade (quando visam uma boa cobertura de teste)?
Den
Além disso, o que você quer dizer com "acoplamento" aqui? Como isso se manifestaria em uma arquitetura de microsserviços, por exemplo?
Den
@ Den 1) boa pergunta, no entanto, acho que está fora do escopo do OP e da minha resposta. 2) Quero dizer acoplamento nesse sentido ; resumidamente, sistemas de tipos diferentes impõem restrições diferentes (incompatíveis) ao código escrito nesse idioma. Desculpe, não consigo responder à última pergunta - não entendo o que há de especial em microsserviços nesse sentido.
2
@ Den: Ponto muito bom: eu frequentemente observo que os testes de unidade que escrevo em Python cobrem erros que seriam capturados por um compilador em uma linguagem de tipo estaticamente.
Giorgio
@MattFenwick: Você escreveu que é uma vantagem que "... para uma linguagem dinâmica, um sistema de tipo estático não precise ser projetado, implementado, testado e mantido". e Den observaram que muitas vezes é necessário projetar e testar seus tipos diretamente no seu código. Portanto, o esforço não é removido, mas movido do design do idioma para o código do aplicativo.
Giorgio