Buscando esclarecimentos sobre aparentes contradições em relação a linguagens de tipo fraco

178

Acho que entendo digitação forte , mas toda vez que procuro exemplos para a digitação fraca, acabo encontrando exemplos de linguagens de programação que simplesmente coagem / convertem tipos automaticamente.

Por exemplo, neste artigo chamado Digitação: Forte vs. Fraco, Estático vs. Dinâmico diz que o Python é fortemente digitado porque você recebe uma exceção se tentar:

Pitão

1 + "1"
Traceback (most recent call last):
File "", line 1, in ? 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

No entanto, isso é possível em Java e em C #, e não os consideramos fracamente digitados apenas para isso.

Java

  int a = 10;
  String b = "b";
  String result = a + b;
  System.out.println(result);

C #

int a = 10;
string b = "b";
string c = a + b;
Console.WriteLine(c);

Neste outro artigo chamado Weakly Type Languages, o autor diz que Perl é digitado de maneira fraca, simplesmente porque eu posso concatenar uma string para um número e vice-versa sem nenhuma conversão explícita.

Perl

$a=10;
$b="a";
$c=$a.$b;
print $c; #10a

Portanto, o mesmo exemplo faz com que Perl seja digitado fracamente, mas não Java e C # ?.

Nossa, isso é confuso insira a descrição da imagem aqui

Os autores parecem sugerir que uma linguagem que impede a aplicação de certas operações em valores de tipos diferentes é fortemente tipada e, ao contrário, significa tipicamente fracamente.

Portanto, em algum momento, senti-me solicitado a acreditar que se um idioma fornece muitas conversões automáticas ou coerção entre tipos (como perl) pode acabar sendo considerado com uma tipagem fraca, enquanto outros idiomas que fornecem apenas algumas conversões podem acabar sendo considerado fortemente tipado.

Estou inclinado a acreditar, no entanto, que devo estar errado nessa interpretação, simplesmente não sei por que nem como explicá-la.

Então, minhas perguntas são:

  • O que realmente significa para um idioma ser verdadeiramente fracamente digitado?
  • Você poderia mencionar bons exemplos de digitação fraca que não estão relacionados à conversão automática / coerção automática feita pelo idioma?
  • Um idioma pode ser fracamente digitado e fortemente digitado ao mesmo tempo?
Edwin Dalorzo
fonte
8
Tipagem forte versus fraca é tudo sobre conversão de tipo (o que mais poderia ser?) Se você quiser um exemplo de um idioma fraco "muito", assista a isto: destroyallsoftware.com/talks/wat .
Wilduck 29/03
2
@Wildduck Todos os idiomas fornecem conversões de tipo, mas nem todos são considerados de tipo fraco. Meus exemplos mostrados abaixo demonstram como os programadores consideram um idioma com baixa digitação, com base nos mesmos exemplos possíveis em outros idiomas considerados com forte digitação. Como tal, minha pergunta ainda prevalece. Qual é a diferença?
Edwin Dalorzo 29/03/12
1
A resposta curta, eu acho, é que "Digitação" não é um estado binário. Java e C # são mais tipificados, mas não absolutamente.
precisa
3
Eu acredito que isso é mais adequado para Engenharia de Software .
zzzzBov
4
@Brendan Que tal somar um float e um número inteiro? O número inteiro não é coagido a flutuar em Python? Você diria agora que o Python não é absolutamente fortemente digitado?
Edwin Dalorzo

Respostas:

210

ATUALIZAÇÃO: Esta questão foi o assunto do meu blog em 15 de outubro de 2012. Obrigado pela ótima pergunta!


O que realmente significa para um idioma ser "digitado fracamente"?

Significa "esta linguagem usa um sistema de tipos que considero desagradável". Uma linguagem "fortemente tipada", ao contrário, é uma linguagem com um sistema de tipos que eu acho agradável.

Os termos são essencialmente sem sentido e você deve evitá-los. A Wikipedia lista onze significados diferentes para "fortemente tipado", vários dos quais são contraditórios. Isso indica que as chances de confusão que estão sendo criadas são altas em qualquer conversa envolvendo o termo "tipicamente fortemente" ou "tipicamente fraco".

Tudo o que você realmente pode dizer com certeza é que uma linguagem "fortemente tipada" em discussão tem alguma restrição adicional no sistema de tipos, em tempo de execução ou compilação, que falta uma linguagem "tipicamente fraca" em discussão. O que essa restrição pode ser não pode ser determinada sem outro contexto.

Em vez de usar "fortemente digitado" e "pouco digitado", você deve descrever em detalhes que tipo de segurança de tipo você quer dizer. Por exemplo, C # é uma linguagem de tipo estaticamente e uma linguagem segura de tipo e uma linguagem segura de memória , na maior parte. O C # permite que todas as três formas de digitação "forte" sejam violadas. O operador de conversão viola a digitação estática; ele diz ao compilador "Eu sei mais sobre o tipo de tempo de execução dessa expressão do que você". Se o desenvolvedor estiver errado, o tempo de execução lançará uma exceção para proteger a segurança do tipo. Se o desenvolvedor desejar quebrar a segurança de tipo ou a segurança de memória, poderá fazê-lo desligando o sistema de segurança de tipo criando um bloco "inseguro". Em um bloco não seguro, você pode usar a magia do ponteiro para tratar um int como um flutuador (violando a segurança do tipo) ou para gravar na memória que você não possui. (Violando a segurança da memória.)

O C # impõe restrições de tipo que são verificadas no tempo de compilação e no tempo de execução, tornando-o um idioma "fortemente tipado" comparado aos idiomas que fazem menos verificação no tempo de compilação ou menos verificação no tempo de execução. O C # também permite que você, em circunstâncias especiais, execute uma execução final com essas restrições, tornando-o um idioma "de tipo fraco" em comparação com os idiomas que não permitem a execução final.

Qual é mesmo? É impossível dizer; depende do ponto de vista do falante e de sua atitude em relação aos vários recursos do idioma.

Eric Lippert
fonte
14
@edalorzo: baseia-se no gosto e nas opiniões pessoais sobre (1) quais aspectos da teoria dos tipos são relevantes e quais são irrelevantes e (2) se um idioma é necessário para impor ou apenas incentivar restrições de tipo. Como eu apontei, pode-se dizer razoavelmente que o C # é fortemente tipado porque permite e encoraja a digitação estática, e também pode-se dizer que é razoavelmente digitado porque permite a possibilidade de violar a segurança do tipo.
Eric Lippert
4
@edalorzo: Quanto à montagem, novamente, é uma questão de opinião. Um compilador de linguagem assembly não permitirá que você mova um dobro de 64 bits da pilha para um registro de 32 bits; Isso permitirá que você mova um ponteiro de 32 bits para um dobro de 64 bits da pilha em um registro de 32 bits. Nesse sentido, a linguagem é "typesafe" - impõe uma restrição à legalidade do programa com base em uma classificação de tipo de dados. Se essa restrição é "forte" ou "fraca" é uma questão de opinião, mas é claramente uma restrição.
Eric Lippert
2
Acho que entendo o seu ponto agora, uma linguagem verdadeiramente de tipo fraco teria que ser totalmente sem tipo ou monotipada, o que na vida real é praticamente impossível. Como tal, qualquer idioma possui uma certa definição de tipos, que são seguros e, dependendo do número de falhas que o idioma fornece para violar ou manipular seus dados ou tipos de dados, você pode acabar considerando-o mais ou menos fracamente digitado, talvez até somente em certos contextos.
Edwin Dalorzo 29/03/12
7
@edalorzo: Correto. Por exemplo, o cálculo lambda sem tipo é o mais fraco possível. Toda função é uma função de uma função para uma função; qualquer dado pode ser passado para qualquer função sem restrição, porque tudo é "do mesmo tipo". A validade de uma expressão no cálculo lambda sem tipo depende apenas de sua forma sintática, não de uma análise semântica que classifique certas expressões como tendo certos tipos.
Eric Lippert
3
@ Mark Eu daria a ele mais um +1 por prever que todos dariam interpretações diferentes sobre o assunto. Este "tipagem fraca" parece ser um "conceito mítico" ou uma "lenda urbana", todo mundo já viu, mas ninguém pode provar que ela existe :-)
Edwin Dalorzo
64

Como outros observaram, os termos "fortemente digitado" e "fracamente digitado" têm tantos significados diferentes que não há uma resposta única para sua pergunta. No entanto, como você mencionou especificamente o Perl na sua pergunta, deixe-me tentar explicar em que sentido o Perl é fracamente digitado.

O ponto é que, em Perl, não existe uma "variável inteira", uma "variável flutuante", uma "variável de string" ou uma "variável booleana". De fato, na medida em que o usuário pode (normalmente) dizer, não há ainda inteiro, float, string ou boolean valores : tudo que você tem são "escalares", que são todas essas coisas ao mesmo tempo. Então você pode, por exemplo, escrever:

$foo = "123" + "456";           # $foo = 579
$bar = substr($foo, 2, 1);      # $bar = 9
$bar .= " lives";               # $bar = "9 lives"
$foo -= $bar;                   # $foo = 579 - 9 = 570

Obviamente, como você observou corretamente, tudo isso pode ser visto apenas como coerção de tipo. Mas o ponto é que, em Perl, os tipos são sempre coagidos. De fato, é muito difícil para um usuário dizer qual pode ser o "tipo" interno de uma variável: na linha 2 do meu exemplo acima, perguntando se o valor de $baré a sequência "9"ou o número 9é praticamente sem sentido, pois, como No que diz respeito a Perl, essas são a mesma coisa . Na verdade, é ainda possível para um escalar Perl ter internamente tanto uma string e um valor numérico ao mesmo tempo, como é por exemplo o caso para $foodepois linha 2 acima.

O outro lado disso tudo é que, como as variáveis ​​Perl não são tipadas (ou melhor, não expõem seu tipo interno ao usuário), os operadores não podem ser sobrecarregados para fazer coisas diferentes para diferentes tipos de argumentos; você não pode simplesmente dizer "esse operador fará X para números e Y para seqüências de caracteres", porque o operador não pode (não vai) dizer que tipo de valores são seus argumentos.

Assim, por exemplo, o Perl possui e precisa de um operador de adição numérica ( +) e um operador de concatenação de strings ( .): como você viu acima, é perfeitamente bom adicionar strings ( "1" + "2" == "3") ou concatenar números ( 1 . 2 == 12). Da mesma forma, os operadores de comparação numérica ==, !=, <, >, <=, >=e <=>comparar os valores numéricos de seus argumentos, enquanto os operadores de comparação de string eq, ne, lt, gt, le, gee cmpcompará-los lexicographically como strings. Então 2 < 10, mas 2 gt 10(mas "02" lt 10enquanto "02" == 2). (Lembre-se de que outras línguas, como o JavaScript, tentam acomodar a digitação fraca do tipo Perl enquantotambém sobrecarregando o operador. Isso geralmente leva à feiúra, como a perda de associatividade por +.)

(O ponto principal aqui é que, por razões históricas, o Perl 5 tem alguns casos de canto, como os operadores lógicos bit a bit, cujo comportamento depende da representação interna de seus argumentos. Esses são geralmente considerados uma falha de design irritante, pois a representação interna pode mudar por razões surpreendentes e, portanto, prever o que esses operadores fazem em uma determinada situação pode ser complicado.)

Tudo o que disse, pode-se argumentar que Perl faz ter tipos fortes; eles não são apenas o tipo de tipo que você poderia esperar. Especificamente, além do tipo "escalar" discutido acima, o Perl também possui dois tipos estruturados: "matriz" e "hash". Essas são muito distintas dos escalares, a ponto de as variáveis ​​Perl terem sigilos diferentes, indicando seu tipo ( $para escalares, @matrizes, %hashes) 1 . Não são regras de coerção entre esses tipos, assim que você pode escrever, por exemplo %foo = @bar, mas muitos deles são bastante com perdas: por exemplo, $foo = @baratribui o comprimento da matriz @barpara$foo, não seu conteúdo. (Além disso, existem alguns outros tipos estranhos, como botões de escrever e identificadores de E / S, que você não costuma ver expostos.)

Além disso, uma pequena brecha nesse belo design é a existência de tipos de referência, que são um tipo especial de escalares (e que podem ser distinguidos dos escalares normais, usando o refoperador). É possível usar referências como escalares normais, mas seus valores numéricos / de seqüência de caracteres não são particularmente úteis e tendem a perder sua referência especial se você os modificar usando operações escalares normais. Além disso, qualquer variável Perl 2 pode ser blesseditada em uma classe, transformando-a em um objeto dessa classe; o sistema de classes OO em Perl é um tanto ortogonal ao sistema de tipo primitivo (ou falta de tipografia) descrito acima, embora também seja "fraco" no sentido de seguir a digitação do patoparadigma. A opinião geral é que, se você verificar a classe de um objeto no Perl, estará fazendo algo errado.


1 Na verdade, o sigil indica o tipo do valor que está sendo acessado, de modo que, por exemplo, o primeiro escalar na matriz @fooé indicado $foo[0]. Veja perlfaq4 para mais detalhes.

2 Objetos no Perl são (normalmente) acessados ​​através de referências a eles, mas o que realmente é blesseditado é a variável (possivelmente anônima) para a qual a referência aponta. No entanto, a bênção é de fato uma propriedade da variável, não de seu valor, por exemplo, atribuir a variável abençoada real a outra apenas fornece uma cópia superficial e não abençoada dela. Veja perlobj para mais detalhes.

Ilmari Karonen
fonte
19

Além do que Eric disse, considere o seguinte código C:

void f(void* x);

f(42);
f("hello");

Ao contrário de linguagens como Python, C #, Java ou outros enfeites, o texto acima é fracamente digitado porque perdemos informações de tipo. Eric apontou corretamente que em C # podemos contornar o compilador lançando, dizendo efetivamente: "Eu sei mais sobre o tipo dessa variável que você".

Mas, mesmo assim, o tempo de execução ainda verificará o tipo! Se a conversão for inválida, o sistema de tempo de execução a capturará e lançará uma exceção.

Com o apagamento do tipo, isso não acontece - as informações do tipo são jogadas fora. Um elenco void*em C faz exatamente isso. A esse respeito, o exposto acima é fundamentalmente diferente de uma declaração de método de C # como void f(Object x).

(Tecnicamente, o C # também permite o apagamento de tipo por meio de código não seguro ou empacotamento.)

Isso é o mais fraco possível. Tudo o resto é apenas uma questão de verificação de tipo estático versus dinâmico, ou seja, da época em que um tipo é verificado.

Konrad Rudolph
fonte
1
+1 Bom ponto, agora você me fez pensar no apagamento de tipo como um recurso que também pode implicar em "digitação fraca". Também há apagamento de tipo em Java e, em tempo de execução, o sistema de tipos permitirá violar restrições que o compilador nunca aprovaria. O exemplo C é excelente para ilustrar o ponto.
Edwin Dalorzo 29/03/12
1
Concordado, há camadas para a cebola, ou o inferno. Parece uma definição mais significativa de fraqueza de tipo.
precisa
1
@edalorzo Eu não acho que isso é bastante a mesma desde embora Java permite contornar o compilador, o sistema de tipo de tempo de execução ainda vai pegar a violação. Portanto, o sistema do tipo Java Runtime é fortemente tipado nesse sentido (há exceções, por exemplo, onde a reflexão pode ser usada para burlar o controle de acesso).
Konrad Rudolph
1
@edalorzo Você só pode contornar o compilador dessa maneira, não o sistema de tempo de execução. É importante perceber que linguagens como Java e C # (e até certo ponto também C ++) possuem um sistema de tipos que é garantido duas vezes: uma vez no tempo de compilação e outra no tempo de execução. void*rompe as duas verificações de tipo. O apagamento de tipo genérico não, apenas evita as verificações em tempo de compilação. É exatamente como elencos explícitos (mencionados por Eric) a esse respeito.
Konrad Rudolph
1
@edalorzo Re sua confusão: não devemos. A distinção é fluente. E sim, o apagamento de tipo faz com que o Java seja digitado fracamente a esse respeito. Meu argumento foi que, mesmo com o apagamento de tipo genérico, você ainda não pode contornar as verificações de tipo de tempo de execução, a menos que você também use reflexão .
Konrad Rudolph
14

Um exemplo perfeito vem do artigo da Wikipedia sobre Strong Typing :

Geralmente, a digitação forte implica que a linguagem de programação impõe severas restrições à mistura que é permitida.

Digitação fraca

a = 2
b = "2"

concatenate(a, b) # returns "22"
add(a, b) # returns 4

Digitação forte

a = 2
b = "2"

concatenate(a, b) # Type Error
add(a, b) # Type Error
concatenate(str(a), b) #Returns "22"
add(a, int(b)) # Returns 4

Observe que uma linguagem de digitação fraca pode misturar tipos diferentes sem erros. Um idioma de tipo forte exige que os tipos de entrada sejam os tipos esperados. Em uma linguagem de texto forte, um tipo pode ser convertido ( str(a)converte um número inteiro em uma string) ou cast ( int(b)).

Tudo isso depende da interpretação da digitação.

SaulBack
fonte
3
Mas isso leva aos exemplos contraditórios fornecidos na pergunta. Uma linguagem fortemente tipada pode incluir coerção implícita, o que significa que um dos dois exemplos de "Erro de tipo" é convertido automaticamente para o relevante dos dois segundos exemplos, mas geralmente essa linguagem ainda é fortemente tipada.
Mark Hurd
3
Verdade. Eu acho que você poderia dizer que existem vários graus de digitação forte e digitação fraca. Conversão implícita pode significar que o idioma é menos fortemente digitado do que um idioma que não faz conversão implícita.
SaulBack
4

Eu gostaria de contribuir para a discussão com minha própria pesquisa sobre o assunto, à medida que outros comentam e contribuem. Eu tenho lido suas respostas e seguido suas referências e encontrei informações interessantes. Como sugerido, é provável que a maior parte disso seja melhor discutida no fórum dos programadores, pois parece ser mais teórico do que prático.

Do ponto de vista teórico, acho que o artigo de Luca Cardelli e Peter Wegner, intitulado Sobre a compreensão de tipos, abstração de dados e polimorfismo, tem um dos melhores argumentos que já li.

Um tipo pode ser visto como um conjunto de roupas (ou uma armadura) que protege uma representação não tipificada subjacente do uso arbitrário ou não intencional. Ele fornece uma cobertura protetora que oculta a representação subjacente e restringe a maneira como os objetos podem interagir com outros objetos. Em um sistema não tipado, objetos não tipados são nus , pois a representação subjacente é exposta para todos verem. Violar o sistema de tipos envolve remover o conjunto de roupas de proteção e operar diretamente na representação nua.

Essa afirmação parece sugerir que a digitação fraca nos permitiria acessar a estrutura interna de um tipo e manipulá-la como se fosse outra coisa (outro tipo). Talvez o que possamos fazer com código não seguro (mencionado por Eric) ou com ponteiros apagados por tipo c mencionados por Konrad.

O artigo continua ...

Os idiomas em que todas as expressões são consistentes com o tipo são chamados de idiomas fortemente tipados. Se um idioma é fortemente digitado, seu compilador pode garantir que os programas que ele aceita serão executados sem erros de tipo. Em geral, devemos nos esforçar para digitar com firmeza e adotar a digitação estática sempre que possível. Observe que toda linguagem de tipo estaticamente é fortemente tipada, mas o inverso não é necessariamente verdadeiro.

Como tal, digitação forte significa ausência de erros de tipo, posso apenas assumir que digitação fraca significa o contrário: a provável presença de erros de tipo. Em tempo de execução ou tempo de compilação? Parece irrelevante aqui.

O engraçado é que, de acordo com essa definição, uma linguagem com fortes coerções de tipos como Perl seria considerada fortemente tipada, porque o sistema não está falhando, mas está lidando com os tipos, coagindo-os em equivalências apropriadas e bem definidas.

Por outro lado, eu poderia dizer que o subsídio ClassCastExceptioneArrayStoreException (em Java) e InvalidCastException,ArrayTypeMismatchException (em C #) indicaria um nível de tipagem fraca, pelo menos em tempo de compilação? A resposta de Eric parece concordar com isso.

Em um segundo artigo chamado Typeful Programming fornecido em uma das referências fornecidas em uma das respostas nesta pergunta, Luca Cardelli investiga o conceito de violação de tipo:

A maioria das linguagens de programação do sistema permite violações arbitrárias do tipo, algumas indiscriminadamente, outras apenas em partes restritas de um programa. As operações que envolvem violações de tipo são denominadas incorretas. As violações de tipo se enquadram em várias classes [entre as quais podemos mencionar]:

Coerções de valor básico : incluem conversões entre números inteiros, booleanos, caracteres, conjuntos, etc. Não há necessidade de violações de tipo aqui, porque interfaces internas podem ser fornecidas para executar as coerções da maneira correta.

Dessa forma, coerções de tipo como as fornecidas pelos operadores podem ser consideradas violações de tipo, mas, a menos que quebrem a consistência do sistema de tipos, podemos dizer que elas não levam a um sistema de tipo fraco.

Com base nisso, nem Python, Perl, Java ou C # são fracamente tipados.

Cardelli menciona dois tipos de difamação que considero muito bem casos de digitação realmente fraca:

Endereço aritmético. Se necessário, deve haver uma interface interna (incorreta), fornecendo as operações adequadas em endereços e conversões de tipo. Várias situações envolvem ponteiros no heap (muito perigoso ao realocar coletores), ponteiros para a pilha, ponteiros para áreas estáticas e ponteiros para outros espaços de endereço. Às vezes, a indexação de array pode substituir a aritmética do endereço. Mapeamento de memória. Isso envolve olhar para uma área da memória como uma matriz não estruturada, embora contenha dados estruturados. Isso é típico de alocadores e coletores de memória.

Esse tipo de coisa possível em idiomas como C (mencionado por Konrad) ou através de código inseguro em .Net (mencionado por Eric) realmente implicaria digitação fraca.

Acredito que a melhor resposta até agora é a de Eric, porque a definição desses conceitos é muito teórica e, quando se trata de uma linguagem específica, as interpretações de todos esses conceitos podem levar a diferentes conclusões discutíveis.

Edwin Dalorzo
fonte
4

A digitação fraca realmente significa que uma alta porcentagem de tipos pode ser implicitamente coagida, tentando adivinhar o que o codificador pretendia.

A digitação forte significa que os tipos não são coagidos ou, pelo menos, coagidos menos.

A digitação estática significa que os tipos de suas variáveis ​​são determinados em tempo de compilação.

Muitas pessoas recentemente confundiram "manifestamente digitado" com "fortemente digitado". "Digitado manifestamente" significa que você declara explicitamente os tipos de suas variáveis.

O Python é tipicamente fortemente tipado, embora você possa usar quase qualquer coisa em um contexto booleano, e os booleanos possam ser usados ​​em um contexto inteiro e você possa usar um número inteiro em um contexto flutuante. Não é digitado manifestamente, porque você não precisa declarar seus tipos (exceto o Cython, que não é inteiramente python, embora interessante). Também não é digitado estaticamente.

C e C ++ são tipicamente digitados, estaticamente e um pouco fortemente tipados, porque você declara seus tipos, os tipos são determinados em tempo de compilação e você pode misturar números inteiros e ponteiros, números inteiros e duplos, ou mesmo converter um ponteiro para um tipo em um ponteiro para outro tipo.

Haskell é um exemplo interessante, porque não é manifestamente digitado, mas também estático e fortemente tipado.

user1277476
fonte
+1 Porque eu gosto do termo cunhado "manifestamente digitado", que categoriza linguagens como Java e C #, nas quais é necessário declarar explicitamente tipos e distingui-los de outras linguagens de tipo estaticamente, como Haskell e Scala, em que a inferência de tipos desempenha um papel importante e isso normalmente confunde as pessoas, como você diz, e as faz acreditar que esses idiomas são de tipo dinâmico.
Edwin Dalorzo 19/10/12
3

A digitação forte <=> fraca não é apenas sobre o continuum de quanto ou quão pouco dos valores são coagidos automaticamente pelo idioma de um tipo de dados para outro, mas quão forte ou fracamente o real valores são digitados. Em Python e Java, e principalmente em C #, os valores têm seus tipos definidos em pedra. No Perl, nem tanto - existem realmente apenas alguns tipos diferentes de valores para armazenar em uma variável.

Vamos abrir os casos um por um.


Pitão

No exemplo do Python 1 + "1", o +operador chama o __add__tipo for, intfornecendo a string "1"como argumento - no entanto, isso resulta em NotImplemented:

>>> (1).__add__('1')
NotImplemented

Em seguida, o intérprete tenta o __radd__comando str:

>>> '1'.__radd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__radd__'

Como falha, o +operador falha com o resultado TypeError: unsupported operand type(s) for +: 'int' and 'str'. Como tal, a exceção não diz muito sobre digitação forte, mas o fato de o operador + não coagir seus argumentos automaticamente para o mesmo tipo é um indicador do fato de que Python não é a linguagem de tipo mais fraco no continuum.

Por outro lado, em Python 'a' * 5 é implementado:

>>> 'a' * 5
'aaaaa'

Isso é,

>>> 'a'.__mul__(5)
'aaaaa'

O fato de a operação ser diferente requer uma digitação forte - no entanto, o oposto de *coagir os valores a números antes de multiplicar ainda não necessariamente tornaria os valores digitados de maneira fraca.


Java

O exemplo de Java String result = "1" + 1;funciona apenas porque, por uma questão de conveniência, o operador +está sobrecarregado para seqüências de caracteres. O +operador Java substitui a sequência pela criação de um StringBuilder(consulte este ):

String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()

Este é um exemplo de digitação muito estática, sem coerção real - StringBuildertem um métodoappend(Object) que é usado especificamente aqui. A documentação diz o seguinte:

Anexa a representação em cadeia do Object argumento.

O efeito geral é exatamente como se o argumento fosse convertido em uma string pelo método String.valueOf(Object) , e os caracteres dessa sequência foram anexados a essa sequência de caracteres.

Onde String.valueOf então

Retorna a representação em cadeia do argumento Object. [Retorna] se o argumento for null, então uma string igual a "null"; caso contrário, o valor deobj.toString() é retornado.

Portanto, este é um caso de absolutamente nenhuma coerção por parte da linguagem - delegando toda preocupação aos próprios objetos.


C #

De acordo com a resposta de Jon Skeet aqui , o operador +nem está sobrecarregado para a stringclasse - semelhante ao Java, isso é apenas uma conveniência gerada pelo compilador, graças à digitação estática e forte.


Perl

Como o perldata explica,

O Perl possui três tipos de dados internos: escalares, matrizes de escalares e matrizes associativas de escalares, conhecidas como "hashes". Um escalar é uma cadeia única (de qualquer tamanho, limitada apenas pela memória disponível), número ou uma referência a algo (que será discutido em perlref). Matrizes normais são listas ordenadas de escalares indexados por número, começando com 0. Hashes são coleções não ordenadas de valores escalares indexados por sua chave de cadeia associada.

O Perl, no entanto, não possui um tipo de dados separado para números, booleanos, seqüências de caracteres, valores nulos, undefineds, referências a outros objetos etc. - ele só possui um tipo para todos esses, o tipo escalar; 0 é um valor escalar tanto quanto "0". Uma variável escalar que foi definida como uma string pode realmente se transformar em um número e, a partir daí, se comportar de maneira diferente de "apenas uma string", se for acessada em um contexto numérico. O escalar pode conter qualquer coisa no Perl, é o objeto que existe no sistema. enquanto em Python os nomes se referem apenas aos objetos, em Perl os valores escalares nos nomes são objetos alteráveis. Além disso, o sistema de tipo orientado a objetos é colado em cima disso: existem apenas três tipos de dados em perl - escalares, listas e hashes. Um objeto definido pelo usuário no Perl é uma referência (que é um ponteiro para qualquer um dos 3 anteriores)blessdistribuído para um pacote - você pode pegar esse valor e abençoá-lo com qualquer classe a qualquer momento que desejar.

O Perl ainda permite que você altere as classes de valores por capricho - isso não é possível no Python, para criar um valor de alguma classe, você precisa construir explicitamente o valor pertencente a essa classe object.__new__ou similar. No Python, você não pode realmente mudar a essência do objeto após a criação; no Perl, você pode fazer qualquer coisa:

package Foo;
package Bar;

my $val = 42;
# $val is now a scalar value set from double
bless \$val, Foo;
# all references to $val now belong to class Foo
my $obj = \$val;
# now $obj refers to the SV stored in $val
# thus this prints: Foo=SCALAR(0x1c7d8c8)
print \$val, "\n"; 
# all references to $val now belong to class Bar
bless \$val, Bar;
# thus this prints Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# we change the value stored in $val from number to a string
$val = 'abc';
# yet still the SV is blessed: Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# and on the course, the $obj now refers to a "Bar" even though
# at the time of copying it did refer to a "Foo".
print $obj, "\n";

assim, a identidade do tipo está fracamente ligada à variável e pode ser alterada através de qualquer referência em tempo real. De fato, se você fizer

my $another = $val;

\$anothernão possui a identidade de classe, mesmo que \$valainda dê a referência abençoada.


TL; DR

Há muito mais sobre digitação fraca no Perl do que apenas coerções automáticas, e é mais sobre o fato de que os tipos dos valores em si não são gravados em pedra, ao contrário do Python, que é uma linguagem de tipo dinâmico e muito fortemente tipada. Que python dá TypeErroron 1 + "1"é uma indicação de que a linguagem é fortemente tipado, embora o contrário de fazer algo útil, como em Java ou C # não impede que eles, sendo línguas rigidez.

Antti Haapala
fonte
Isso é totalmente confuso. O fato de as variáveis Perl 5 não terem tipos não influencia os valores , que sempre têm um tipo.
Jim Balter
@ JimBalter bem, sim, um valor tem um tipo em que é uma sequência ou um número e pode se comportar de maneira diferente em algum contexto, dependendo se a variável escalar continha uma sequência ou um número; mas o valor contido em uma variável pode mudar de tipo apenas acessando a variável e, como o próprio valor vive na variável, os próprios valores podem ser considerados mutáveis ​​entre os tipos.
Antti Haapala
Valores não mudam de tipo - isso é incoerente; um valor é sempre de um tipo . O valor que uma variável contém pode mudar. Uma mudança de 1 a "1" é tanto uma mudança de valor como uma mudança de 1 para 2.
Jim Balter
Uma linguagem de tipo fraco, como Perl, permite que o tipo anterior de alteração de valor ocorra implicitamente, dependendo do contexto. Mas mesmo o C ++ permite essas conversões implícitas por meio de definições do operador. A digitação fraca é uma propriedade muito informal e realmente não é uma maneira útil de descrever idiomas, como apontou Eric Lippert.
Jim Balter
PS Pode ser demonstrado que, mesmo em Perl, <dígitos> e "<dígitos>" têm valores diferentes, não apenas tipos diferentes. O Perl faz com que <dígitos> e "<dígitos>" pareçam ter o mesmo valor na maioria dos casos por meio de conversões implícitas , mas a ilusão não está completa; por exemplo "12" | "34" é 36, enquanto 12 | 34 é 46. Outro exemplo é que "00" é numericamente igual a 00 na maioria dos contextos, mas não no contexto booleano, onde "00" é verdadeiro, mas 00 é falso.
Jim Balter
1

Como muitos outros expressaram, toda a noção de digitação "forte" vs "fraca" é problemática.

Como arquétipo, o Smalltalk é digitado com muita força - sempre gera uma exceção se uma operação entre dois objetos for incompatível. No entanto, suspeito que poucos nesta lista chamariam o Smalltalk de uma linguagem fortemente tipada, porque é digitada dinamicamente.

Acho a noção de digitação "estática" versus "dinâmica" mais útil do que "forte" versus "fraca". Uma linguagem de tipo estaticamente possui todos os tipos descobertos em tempo de compilação, e o programador deve declarar explicitamente, caso contrário.

Contraste com um idioma digitado dinamicamente, onde a digitação é realizada em tempo de execução. Isso geralmente é um requisito para linguagens polimórficas, para que decisões sobre se uma operação entre dois objetos seja legal não precisam ser decididas pelo programador com antecedência.

Em linguagens polimórficas e de tipo dinâmico (como Smalltalk e Ruby), é mais útil pensar em um "tipo" como uma "conformidade com o protocolo". Se um objeto obedece a um protocolo da mesma forma que outro objeto, mesmo que os dois objetos não compartilhem nenhuma herança, mixins ou outro vodu, eles são considerados o mesmo "tipo" pelo sistema de tempo de execução. Mais corretamente, um objeto em tais sistemas é autônomo e pode decidir se faz sentido responder a uma mensagem específica referente a um argumento específico.

Deseja um objeto que possa dar alguma resposta significativa à mensagem "+" com um argumento de objeto que descreva a cor azul? Você pode fazer isso em idiomas tipificados dinamicamente, mas é problemático em idiomas tipicamente estatísticos.

Jan Steinman
fonte
3
Eu acho que o conceito de digitação dinâmica versus estática não está em discussão. Embora eu deva dizer que não acredito que o polimorfismo seja de alguma forma prejudicado nas linguagens do tipo estaticamente. Por fim, o sistema de tipos verifica se uma determinada operação é aplicável aos operandos especificados, seja em tempo de execução ou em tempo de compilação. Além disso, outras formas de polimorfismo, como funções e classes paramétricas, permitem combinar tipos em linguagens estaticamente de maneiras que você descreveu como muito difíceis quando comparadas a tipadas dinamicamente, ainda mais agradáveis ​​se for fornecida inferência de tipo.
Edwin Dalorzo 04/04
0

Gosto da resposta de @Eric Lippert , mas para abordar a questão - linguagens fortemente tipadas geralmente têm conhecimento explícito dos tipos de variáveis ​​em cada ponto do programa. Linguagens com tipos fracos não o fazem, portanto, elas podem tentar executar uma operação que talvez não seja possível para um tipo específico. Ele acha que a maneira mais fácil de ver isso é em uma função. C ++:

void func(string a) {...}

aSabe-se que a variável é do tipo string e qualquer operação incompatível será capturada no momento da compilação.

Pitão:

def func(a)
  ...

A variável apode ser qualquer coisa e podemos ter um código que chama um método inválido, que só será pego em tempo de execução.

Lubo Antonov
fonte
12
Eu acho que você pode estar confundindo digitação dinâmica versus digitação estática com digitação forte vs digitação fraca. Nas duas versões do seu código, os sistemas do tipo tempo de execução sabem muito bem que a é uma sequência. É apenas que, no primeiro caso, o compilador pode dizer que, no segundo, não pode. Mas isso não torna nenhum desses idiomas de tipo fraco.
Edwin Dalorzo 29/03