Por que linguagens digitadas dinamicamente não permitem que o desenvolvedor especifique o tipo?

14

As linguagens de tipo dinâmico que eu conheço nunca deixam os desenvolvedores especificarem os tipos de variáveis ​​ou, pelo menos, têm um suporte muito limitado para isso.

O JavaScript, por exemplo, não fornece nenhum mecanismo para impor tipos de variáveis ​​quando for conveniente. PHP permitem especificar alguns tipos de argumentos de método, mas não há maneira de usar tipos nativos ( int, string, etc.) para os argumentos, e não há nenhuma maneira de impor tipos para outra coisa senão argumentos.

Ao mesmo tempo, seria conveniente ter a opção de especificar, em alguns casos, o tipo de uma variável em um idioma digitado dinamicamente, em vez de fazer a verificação do tipo manualmente.

Por que existe essa limitação? É por razões técnicas / de desempenho (suponho que seja no caso do JavaScript) ou apenas por razões políticas (que é, acredito, o caso do PHP)? Esse é o caso de outras linguagens de tipo dinâmico que eu não conheço?


Edit: seguindo as respostas e os comentários, aqui está um exemplo para um esclarecimento: digamos que temos o seguinte método no PHP simples:

public function CreateProduct($name, $description, $price, $quantity)
{
    // Check the arguments.
    if (!is_string($name)) throw new Exception('The name argument is expected to be a string.');
    if (!is_string($description)) throw new Exception('The description argument is expected to be a string.');
    if (!is_float($price) || is_double($price)) throw new Exception('The price argument is expected to be a float or a double.');
    if (!is_int($quantity)) throw new Exception('The quantity argument is expected to be an integer.');

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Com alguns esforços, isso pode ser reescrito como (também consulte Programação por contratos em PHP ):

public function CreateProduct($name, $description, $price, $quantity)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'name' => array('value' => $name, 'type' => VTYPE_STRING),
        'description' => array('value' => $description, 'type' => VTYPE_STRING),
        'price' => array('value' => $price, 'type' => VTYPE_FLOAT_OR_DOUBLE),
        'quantity' => array('value' => $quantity, 'type' => VTYPE_INT)
    ));

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Mas o mesmo método seria escrito da seguinte forma se o PHP opcionalmente aceitasse tipos nativos para argumentos:

public function CreateProduct(string $name, string $description, double $price, int $quantity)
{
    // Check the arguments.
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Qual é o mais curto para escrever? Qual é mais fácil de ler?

Arseni Mourzenko
fonte
1
Opcionalmente, você pode especificar tipos em alguns idiomas dinamicamente digitados - por exemplo, no Common Lisp.
SK-logic
Muito poucas linguagens digitadas dinamicamente usar moldes para forçar um tipo ...
Trezoid
Alguns fazem. O Objective-C, por exemplo, é digitado dinamicamente, mas você pode declarar um tipo para variáveis ​​e o compilador emitirá avisos se você não obtiver o tipo esperado.
Mipadi
1
Clojure é um exemplo de uma linguagem que é normalmente de tipagem dinâmica, mas você pode opcionalmente dar variáveis tipos através de "tipo dicas" (isto é normalmente feito somente quando necessário para obter os benefícios de informações de tipo de tempo de compilação de desempenho)
mikera
1
Groovy é outro exemplo de uma linguagem de tipo dinâmico que permite que um tipo seja especificado.
Eric Wilson

Respostas:

17

O objetivo de ter uma digitação estática é a capacidade de provar estaticamente que seu programa está correto em relação aos tipos (nota: não totalmente correto em todos os sentidos). Se você tiver um sistema de tipo estático, poderá detectar erros de tipo na maioria das vezes.

Se você tiver apenas informações de tipo parcial, poderá verificar apenas as pequenas partes de um gráfico de chamada onde as informações de tipo estão completas. Mas você gastou tempo e esforço para especificar informações de tipo para peças incompletas, onde elas não podem ajudá-lo, mas podem dar uma falsa sensação de segurança.

Para expressar informações de tipo, você precisa de uma parte do idioma que não possa ser excessivamente simples. Em breve você descobrirá que informações como intnão são suficientes; você vai querer algo como List<Pair<Int, String>>tipos paramétricos, etc. Isso pode ser bastante confuso, mesmo no caso bastante simples de Java.

Então, você precisará manipular essas informações durante a fase de tradução e a fase de execução, porque é bobagem verificar apenas erros estáticos; o usuário espera que as restrições de tipo sempre sejam mantidas, se especificadas. Os idiomas dinâmicos não são tão rápidos quanto são e essas verificações retardarão ainda mais o desempenho. Uma linguagem estática pode gastar muito esforço verificando os tipos, porque só faz isso uma vez; uma linguagem dinâmica não pode.

Agora imagine adicionar e manter tudo isso apenas para que as pessoas às vezes usem esses recursos opcionalmente , detectando apenas uma pequena fração dos erros de tipo. Eu não acho que vale a pena o esforço.

O ponto principal das linguagens dinâmicas é ter uma estrutura muito pequena e muito maleável, na qual você possa facilmente fazer coisas muito mais envolvidas quando executadas em uma linguagem estática: várias formas de correção de macacos usadas para metaprogramação, zombaria e testes, substituição dinâmica de código, etc. Smalltalk e Lisp, ambos muito dinâmicos, levaram ao extremo a enviar imagens de ambiente em vez de construir a partir da fonte. Mas quando você quiser garantir que determinados caminhos de dados sejam seguros para o tipo, adicione asserções e escreva mais testes de unidade.

9000
fonte
1
+1, embora os testes possam mostrar apenas que erros não ocorrem em determinadas situações. Eles são um mau substituto para uma prova de que (tipo) erros são impossíveis.
Ingo
1
@ Ingo: certamente. Porém, linguagens dinâmicas são ótimas para mexer e prototipar rapidamente, onde você expressa idéias relativamente simples muito rapidamente. Se você quiser um código de produção à prova de balas, poderá voltar para uma linguagem estática depois de extrair alguns componentes principais estáveis.
9000
1
@ 9000, não duvido que eles sejam ótimos. Queria apenas salientar que escrever 3 ou 4 testes coxos não é e não pode garantir a segurança do tipo .
Ingo
2
@ 9000, True, e a má notícia é que, mesmo assim, é praticamente impossível. Até o código Haskell ou Agda depende de suposições, como, por exemplo, que a biblioteca usada no tempo de execução está correta. Dito isto, em um projeto com cerca de 1000 LOC espalhados por cerca de uma dúzia de arquivos de código-fonte, é muito legal quando você pode mudar alguma coisa e sabe que o compilador apontará para todas as linhas em que a mudança tiver impacto.
Ingo
4
Testes mal escritos não substituem a verificação de tipo estático: são inferiores. Testes bem escritos também não substituem a verificação de tipo estático: eles são superiores.
Rein Henrichs
8

Na maioria das linguagens dinâmicas, você pode pelo menos testar dinamicamente o tipo de um objeto ou valor.

E existem inferenciadores de tipo estático, verificadores e / ou aplicadores para algumas linguagens dinâmicas: por exemplo

E o Perl 6 suporta um sistema de tipos opcional com digitação estática.


Mas acho que o ponto principal é que muitas pessoas usam linguagens dinamicamente porque são tipadas dinamicamente e, para elas, a digitação estática opcional é muito "hum hum". E muitas outras pessoas as usam porque são "fáceis de usar por não programadores", principalmente como conseqüência da digitação dinâmica da natureza perdoadora. Para eles, a digitação opcional é algo que eles não entenderão ou não se incomodarão em usar.

Se você fosse cínico, poderia dizer que a digitação estática opcional oferece o pior dos dois mundos. Para um fanático do tipo estático, ele não evita todas as falhas do tipo dinâmico. Para um fã do tipo dinâmico, ainda é uma jaqueta reta ... embora com as tiras não esticadas.

Stephen C
fonte
2
Deve-se notar que a verificação de tipos é desaprovada pela maioria das comunidades na maioria das circunstâncias. Use polimorfismo (especificamente, "digitação de pato") ao lidar com hierarquias de objetos, coagir ao tipo esperado, se possível / sensato. Alguns casos permanecem onde simplesmente não faz sentido permitir qualquer tipo, mas em muitos idiomas você recebe uma exceção na maioria desses casos, portanto a verificação de tipo raramente é útil.
4
"as pessoas normalmente usam idiomas dinamicamente porque são digitados dinamicamente" : o JavaScript é usado porque é o único idioma suportado pela maioria dos navegadores. PHP é usado porque é popular.
Arseni Mourzenko
2

O Javascript planejou incluir alguma digitação estática opcional e parece que muitas linguagens dinâmicas maduras estão indo dessa maneira.

O motivo é que, quando você codifica pela primeira vez, deseja ser rápido e digitado dinamicamente. Depois que seu código estiver sólido, funcionando e tiver muitos usos, você deseja bloquear o design para reduzir erros. (isso é benéfico para usuários e desenvolvedores, pois o primeiro receberá uma verificação de erro em suas chamadas e o segundo não interromperá as coisas acidentalmente.

Faz algum sentido para mim, já que geralmente encontro muita verificação de tipo no início de um projeto, muito pouco no final de sua vida útil, independentemente do idioma que eu use;).

Macke
fonte
Não conheço esses planos para incluir digitação estática opcional em JavaScript; mas espero que não tenham sido tão atrozes quanto isso no ActiveScript. o pior de JavaScript e Java.
Javier
Foi planejado para JS 4 (ou ECMAscript 4), mas essa versão foi descartada devido a controvérsia. Tenho certeza de que algo semelhante aparecerá no futuro, em algum idioma. (Em Python você pode classificar-de fazê-lo com decoradores, btw.)
Macke
1
Decoradores adicionam verificação dinâmica de tipo, uma espécie de afirmações. Você não pode obter uma verificação abrangente de tipo estático no Python, por mais que tente, devido ao extremo dinamismo da linguagem.
9000
@ 9000: Isso está correto. No entanto, não acho que a verificação dinâmica de tipo seja ruim (mas prefiro comparações de tipo pato ala JS4), especialmente quando combinadas com testes de unidade, e decoradores de digitação podem ser mais úteis para apoiar IDEs / verificadores de fiapos, se eles padronizado.
Macke
claro que não é ruim! Isto não é uma questão de moralidade. Em algum momento, o tipo deve ser "verificado" de uma maneira ou de outra. Se você escrever * ((duplo *) 0x98765E) em C, a CPU fará isso e verificará se 0x98765E é realmente um ponteiro para um dobro.
Ingo
2

Objetos Python fazer ter um tipo.

Você especifica o tipo ao criar o objeto.

Ao mesmo tempo, seria conveniente ter a opção de especificar, em alguns casos, o tipo de uma variável em um idioma digitado dinamicamente, em vez de fazer a verificação do tipo manualmente.

Na verdade, uma verificação manual do tipo no Python quase sempre é uma perda de tempo e código.

É simplesmente uma prática ruim escrever código de verificação de tipo no Python.

Se um tipo inapropriado foi usado por algum sociopata mal-intencionado, os métodos comuns do Python criarão uma exceção comum quando o tipo não for apropriado.

Você não escreve código, seu programa ainda falha com a TypeError.

Existem casos muito raros em que você deve determinar o tipo em tempo de execução.

Por que existe essa limitação?

Como não é uma "limitação", a pergunta não é verdadeira.

S.Lott
fonte
2
"Objetos Python têm um tipo." - sério? Assim como objetos perl, objetos PHP e todos os outros itens de dados do mundo. A diferença entre a digitação estática e a dinâmica é apenas quando o tipo será verificado, ou seja, quando os erros de tipo se manifestarem. Se eles aparecem como erros do compilador, é digitação estática, se eles aparecem como erros de tempo de execução, é dinâmico.
Ingo
@ Ingo: Obrigado pelo esclarecimento. O problema é que os objetos C ++ e Java podem ser convertidos de um tipo para outro, tornando o tipo de um objeto um pouco obscuro e, portanto, tornando a "verificação de tipo" nesses compiladores um pouco obscura também. Onde a verificação do tipo Python - mesmo que seja em tempo de execução - é muito menos obscura. Além disso, a questão é quase como dizer que linguagens dinamicamente digitadas não têm tipos. A boa notícia é que não comete esse erro comum.
S.Lott 9/11/11
1
Você está certo, digita lançamentos (em contraste com as conversões de tipo, ou seja, ((duplo) 42)) subverte a digitação estática. Eles são necessários quando o sistema de tipos não é poderoso o suficiente. Antes do Java 5, o Java não tinha tipos parmeterizados, então você não podia viver sem as transmissões de tipos. Hoje é muito melhor, mas o sistema de tipos ainda carece de tipos mais elevados, para não falar de polimorfismo mais alto. Eu acho que é bem possível que linguagens dinamicamente digitadas desfrutem de tantos seguidores precisamente porque liberam uma de sistemas de tipos muito estreitos.
Ingo
2

Na maioria das vezes, você não precisa, pelo menos não no nível de detalhe que está sugerindo. No PHP, os operadores que você usa deixam perfeitamente claro o que você espera que os argumentos sejam; é um pouco de supervisão de design, porém, que o PHP converterá seus valores, se possível, mesmo quando você passa uma matriz para uma operação que espera uma string e, como a conversão nem sempre é significativa, às vezes você obtém resultados estranhos ( e é exatamente aqui que as verificações de tipo são úteis). Fora isso, não importa se você adiciona números inteiros 1e / 5ou strings "1"e "5"- o simples fato de estar usando o método+O operador sinaliza para o PHP que você deseja tratar os argumentos como números, e o PHP obedecerá. Uma situação interessante é quando você recebe resultados da consulta do MySQL: Muitos valores numéricos são simplesmente retornados como strings, mas você não notará, pois o PHP os projeta para você sempre que os trata como números.

O Python é um pouco mais rígido quanto a seus tipos, mas, diferentemente do PHP, o Python teve exceções desde o início e o utiliza de forma consistente. O paradigma "mais fácil pedir perdão do que permissão" sugere apenas executar a operação sem verificação de tipo e depender de uma exceção ser levantada quando os tipos não fazem sentido. A única desvantagem disso em que consigo pensar é que, às vezes, você descobre que em algum lugar um tipo não corresponde ao que você espera, mas encontrar o motivo pode ser entediante.

E há outro motivo a considerar: os idiomas dinâmicos não têm um estágio de compilação. Mesmo se você tiver restrições de tipo, elas poderão ser acionadas apenas no tempo de execução, simplesmente porque não há tempo de compilação . Se suas verificações levarem a erros de tempo de execução, é muito mais fácil modelá-las de acordo: Como verificações explícitas (como is_XXX()em PHP ou typeofjavascript) ou lançando exceções (como o Python). Funcionalmente, você tem o mesmo efeito (um erro é sinalizado no tempo de execução quando uma verificação de tipo falha), mas ele se integra melhor ao restante da semântica do idioma. Simplesmente não faz sentido tratar erros de tipo fundamentalmente diferentes de outros erros de tempo de execução em uma linguagem dinâmica.

tdammers
fonte
0

Você pode estar interessado em Haskell - o sistema de tipos infere os tipos do código e você também pode especificar os tipos.

daven11
fonte
5
Haskell é uma ótima linguagem. É de certa forma um oposto para linguagens dinâmicas: você gasta muito tempo descrevendo os tipos, e, geralmente, uma vez que você figurou para seus tipos do programa funciona :)
9000
@ 9000: De fato. Uma vez compilado, geralmente funciona. :)
Macke
@ Macke - para diferentes valores de normalmente , é claro. :-) Para mim, o maior benefício do sistema de tipos e do paradigma funcional é, como apontei em outro lugar, que não é necessário se preocupar se uma mudança em algum lugar afeta silenciosamente algum código dependente em outro lugar - o compilador indicará erros de tipo e o estado mutável simplesmente não existe.
Ingo
0

Como as outras respostas mencionaram, há duas abordagens para digitar ao implementar uma linguagem de programação.

  1. Peça ao programador que lhe diga o que todas as variáveis ​​e funções usam para tipos. Idealmente, você também verifica se as especificações de tipo são precisas. Então, como você sabe que tipo de coisa estará em cada lugar, é possível escrever um código que pressupõe que a coisa apropriada estará lá e usar qualquer estrutura de dados usada para implementar diretamente esse tipo.
  2. Anexe um indicador de tipo aos valores que serão armazenados nas variáveis ​​e passados ​​e retornados das funções. Isso significa que o programador não precisará especificar nenhum tipo para variáveis ​​ou funções, porque os tipos realmente pertencem aos objetos aos quais as variáveis ​​e funções se referem.

Ambas as abordagens são válidas e a utilização depende, em parte, de considerações técnicas, como desempenho, e, em parte, de razões políticas, como o mercado-alvo do idioma.

Larry Coleman
fonte
0

Primeiro de tudo, as linguagens dinâmicas foram criadas principalmente para facilitar o uso. Como você mencionou, é muito bom realizar a conversão de tipos automaticamente e fornecer menos despesas gerais. Mas, ao mesmo tempo, falta problemas de desempenho.

Você pode ficar com os idiomas dinâmicos, caso não se preocupe com o desempenho. Digamos, por exemplo, que o JavaScript seja mais lento quando for necessário realizar muitas conversões de tipo no seu programa, mas isso ajuda a reduzir o número de linhas no seu código.

E para ser mencionado, existem outras linguagens dinâmicas que permitem ao programador especificar o tipo. Por exemplo, o Groovy é uma das famosas linguagens dinâmicas que são executadas na JVM. E tem sido muito famoso nos últimos dias mesmo. Observe que o desempenho do Groovy é igual ao Java.

Espero que ajude você.

Formigas
fonte
-1

Simplesmente não faz sentido fazê-lo.

Por quê?

Como o sistema de tipos de DTLs é precisamente tal que os tipos não podem ser determinados em tempo de compilação. Portanto, o compilador não conseguiu nem verificar se o tipo especificado faria sentido.

Ingo
fonte
1
Por quê? Faz todo o sentido sugerir um compilador sobre quais tipos esperar. Não contradiz nenhuma das restrições do sistema de tipos.
SK-logic
1
Lógica SK: se digitada dinamicamente, significa que toda função / método / operação pega objetos do tipo "Dinâmico", "Qualquer" ou qualquer outra coisa e retorna "Dinâmico", "Qualquer" qualquer coisa, geralmente não há como dizer que um determinado valor sempre será um número inteiro, por exemplo. Portanto, o código de tempo de execução deve procurar não números inteiros de qualquer maneira, como se o tipo fosse "Dinâmico" em primeiro lugar. É exatamente isso que um sistema de tipo estático faz: permite provar que um determinado retorno de variável, campo ou método sempre será de um determinado tipo.
Ingo
@ Ingo, não, há um caminho. Veja como é implementado no Common Lisp, por exemplo. É especialmente útil para variáveis ​​locais - você pode aumentar drasticamente o desempenho introduzindo todas essas dicas de digitação.
SK-logic
@ SK-logic: Talvez você possa me dizer quando e como os erros de tipo são detectados no CL? De qualquer forma, o @MainMa resumiu bastante bem o status quo em sua pergunta: é exatamente o que se poderia esperar de linguagens dinâmicas "puramente".
Ingo
@ Ingo, o que faz você pensar que os tipos são úteis apenas para provar estaticamente a correção? Não é verdade para idiomas como C, onde você tem uma conversão de tipo desmarcada. As anotações de tipo em linguagens dinâmicas são úteis principalmente como dicas do compilador que melhoram o desempenho ou especificam uma representação numérica concreta. Concordo que na maioria dos casos as anotações não devem alterar a semântica do código.
SK-logic
-1

Dê uma olhada em Go, na superfície, ele é estaticamente digitado, mas esses tipos podem ser interfaces que são essencialmente dinâmicas.

dan_waterworth
fonte