Há algumas questões importantes que acho que todas as respostas existentes perderam.
Digitação fraca significa permitir acesso à representação subjacente. Em C, posso criar um ponteiro para caracteres e dizer ao compilador que quero usá-lo como ponteiro para números inteiros:
char sz[] = "abcdefg";
int *i = (int *)sz;
Em uma plataforma little-endian com números inteiros de 32 bits, isso cria i
uma matriz dos números 0x64636261
e 0x00676665
. De fato, você pode até converter ponteiros para números inteiros (do tamanho apropriado):
intptr_t i = (intptr_t)&sz;
E é claro que isso significa que posso substituir a memória em qualquer lugar do sistema. *
char *spam = (char *)0x12345678
spam[0] = 0;
* É claro que os sistemas operacionais modernos usam memória virtual e proteção de página para que eu possa sobrescrever a memória do meu próprio processo, mas não há nada no C que ofereça essa proteção, como qualquer pessoa que já tenha codificado, digamos, no Classic Mac OS ou no Win16 pode lhe dizer.
Lisp tradicional permitia tipos semelhantes de hackery; em algumas plataformas, as células flutuantes e contras de duas palavras eram do mesmo tipo, e você poderia simplesmente passar uma para uma função que espera a outra e ela funcionaria.
Atualmente, a maioria dos idiomas não é tão fraca quanto C e Lisp, mas muitos deles ainda são um pouco vazados. Por exemplo, qualquer linguagem OO que tenha um "downcast" desmarcado, * é um vazamento de tipo: você está basicamente dizendo ao compilador "Eu sei que não lhe dei informações suficientes para saber que isso é seguro, mas tenho certeza é "quando o ponto principal de um sistema de tipos é que o compilador sempre tem informações suficientes para saber o que é seguro.
* Um downcast verificado não torna o sistema de tipos do idioma mais fraco apenas porque move a verificação para o tempo de execução. Se isso acontecesse, o subtipo de polimorfismo (também conhecido como chamadas de função virtuais ou totalmente dinâmicas) seria a mesma violação do sistema de tipos, e acho que ninguém quer dizer isso.
Pouquíssimas linguagens de "script" são fracas nesse sentido. Mesmo em Perl ou Tcl, você não pode pegar uma string e apenas interpretar seus bytes como um número inteiro. * Mas vale a pena notar que no CPython (e da mesma forma para muitos outros intérpretes para muitas linguagens), se você é realmente persistente, pode ser usado ctypes
para carregar libpython
, converter um objeto em id
a POINTER(Py_Object)
e forçar o vazamento do sistema de tipos. Se isso enfraquece ou não o sistema de tipos depende de seus casos de uso - se você estiver tentando implementar uma sandbox de execução restrita no idioma para garantir a segurança, precisará lidar com esses tipos de escapes ...
* Você pode usar uma função como struct.unpack
ler os bytes e criar um novo int de "como C representaria esses bytes", mas obviamente isso não é vazado; até Haskell permite isso.
Enquanto isso, a conversão implícita é realmente algo diferente de um sistema do tipo fraco ou com vazamento.
Todo idioma, mesmo Haskell, tem funções para, digamos, converter um número inteiro em uma string ou um float. Porém, alguns idiomas farão algumas dessas conversões automaticamente para você - por exemplo, em C, se você chamar uma função que deseja a float
e passá-la int
, ela será convertida para você. Definitivamente, isso pode levar a erros com, por exemplo, estouros inesperados, mas eles não são os mesmos tipos de erros que você obtém de um sistema de tipos fracos. E C realmente não está sendo mais fraco aqui; você pode adicionar um int e um float no Haskell ou até concatenar um float em uma string, basta fazer isso de forma mais explícita.
E com linguagens dinâmicas, isso é bastante obscuro. Não existe algo como "uma função que queira flutuar" em Python ou Perl. Mas existem funções sobrecarregadas que fazem coisas diferentes com tipos diferentes, e há uma forte sensação intuitiva de que, por exemplo, adicionar uma string a outra coisa é "uma função que deseja uma string". Nesse sentido, Perl, Tcl e JavaScript parecem fazer muitas conversões implícitas ( "a" + 1
dá a você "a1"
), enquanto o Python faz muito menos ( "a" + 1
gera uma exceção, mas 1.0 + 1
dá a você 2.0
*). É difícil colocar esse sentido em termos formais - por que não deveria haver um +
que tenha uma string e um int, quando obviamente existem outras funções, como a indexação, que fazem?
* Na verdade, no Python moderno, isso pode ser explicado em termos de subtipo OO, pois isinstance(2, numbers.Real)
é verdade. Eu não acho que exista algum sentido em que 2
seja uma instância do tipo string em Perl ou JavaScript ... embora em Tcl, na verdade seja, pois tudo é uma instância de string.
Finalmente, há outra definição completamente ortogonal de digitação "forte" vs. "fraca", em que "forte" significa poderoso / flexível / expressivo.
Por exemplo, Haskell permite definir um tipo que é um número, uma sequência, uma lista desse tipo ou um mapa de sequências para esse tipo, que é uma maneira perfeita de representar qualquer coisa que possa ser decodificada no JSON. Não há como definir esse tipo em Java. Mas pelo menos Java possui tipos paramétricos (genéricos), para que você possa escrever uma função que obtenha uma Lista de T e saiba que os elementos são do tipo T; outras linguagens, como o Java inicial, forçaram você a usar uma lista de objetos e fazer o downcast. Mas pelo menos Java permite criar novos tipos com seus próprios métodos; C apenas permite criar estruturas. E o BCPL nem sequer tinha isso. E assim por diante, até a montagem, onde os únicos tipos têm diferentes comprimentos de bits.
Portanto, nesse sentido, o sistema de tipos de Haskell é mais forte que o Java moderno, que é mais forte que o Java anterior, que é mais forte que o C, que é mais forte que o BCPL.
Então, onde o Python se encaixa nesse espectro? Isso é um pouco complicado. Em muitos casos, a digitação com patos permite simular tudo o que você pode fazer em Haskell e até algumas coisas que não pode; Certamente, os erros são detectados no tempo de execução, em vez do tempo de compilação, mas ainda são detectados. No entanto, há casos em que a digitação com patos não é suficiente. Por exemplo, em Haskell, você pode dizer que uma lista vazia de ints é uma lista de ints, para poder decidir que a redução +
nessa lista deve retornar 0 *; no Python, uma lista vazia é uma lista vazia; não há informações de tipo para ajudá-lo a decidir o que a redução +
deve fazer.
* De fato, Haskell não deixa você fazer isso; se você chamar a função de redução que não assume um valor inicial em uma lista vazia, você recebe um erro. Mas seu sistema de tipos é poderoso o suficiente para que você possa fazer isso funcionar, e o Python não é.