Eu leio código com mais freqüência do que escrevo código e estou assumindo que a maioria dos programadores que trabalham em software industrial faz isso. A vantagem da inferência de tipo que assumo é menos verbosidade e menos código escrito. Mas, por outro lado, se você ler código com mais frequência, provavelmente desejará código legível.
O compilador infere o tipo; existem algoritmos antigos para isso. Mas a verdadeira questão é por que eu, o programador, gostaria de inferir o tipo de minhas variáveis quando leio o código? Não é mais rápido alguém ler o tipo do que pensar que tipo existe?
Edit: Como conclusão, eu entendo por que é útil. Mas na categoria de recursos de linguagem, eu o vejo em um balde com sobrecarga de operador - útil em alguns casos, mas afetando a legibilidade se for abusada.
fonte
Respostas:
Vamos dar uma olhada no Java. Java não pode ter variáveis com tipos inferidos. Isso significa que frequentemente tenho que especificar o tipo, mesmo que seja perfeitamente óbvio para um leitor humano qual é o tipo:
E, às vezes, é simplesmente irritante soletrar todo o tipo.
Essa digitação estática detalhada fica no meu caminho, o programador. A maioria das anotações de tipo são repetições repetitivas e sem conteúdo do que já sabemos. No entanto, eu gosto da digitação estática, pois ela pode realmente ajudar na descoberta de bugs, portanto, a digitação dinâmica nem sempre é uma boa resposta. A inferência de tipo é o melhor dos dois mundos: posso omitir os tipos irrelevantes, mas ainda assim, certifique-se de que meu programa (tipo-) faça check-out.
Embora a inferência de tipo seja realmente útil para variáveis locais, ela não deve ser usada para APIs públicas que precisam ser documentadas sem ambiguidade. E, às vezes, os tipos são realmente críticos para entender o que está acontecendo no código. Nesses casos, seria tolice confiar apenas na inferência de tipo.
Existem muitos idiomas que suportam inferência de tipo. Por exemplo:
C ++. A
auto
palavra-chave aciona a inferência de tipo. Sem ele, escrever os tipos de lambdas ou entradas em contêineres seria um inferno.C #. Você pode declarar variáveis com
var
, o que aciona uma forma limitada de inferência de tipo. Ele ainda gerencia a maioria dos casos em que você deseja inferência de tipo. Em certos lugares, você pode deixar de fora o tipo completamente (por exemplo, em lambdas).Haskell e qualquer idioma da família ML. Embora o sabor específico da inferência de tipo usado aqui seja bastante poderoso, você ainda vê anotações de tipo para funções e por dois motivos: O primeiro é a documentação e o segundo é uma verificação de que a inferência de tipo realmente encontrou os tipos que você esperava. Se houver uma discrepância, provavelmente haverá algum tipo de bug.
fonte
int
, pode ser qualquer tipo numérico, incluindo parchar
. Também não vejo por que você gostaria de especificar o tipo inteiro paraEntry
quando você pode apenas digitar o nome da classe e deixar seu IDE fazer a importação necessária. O único caso em que você precisa especificar o nome inteiro é quando você tem uma classe com o mesmo nome em seu próprio pacote. Mas parece-me um design ruim de qualquer maneira.int
exemplo, estava pensando no (na minha opinião, um comportamento bastante sensato) da maioria dos idiomas que apresentam inferência de tipo. Eles geralmente inferem umint
ouInteger
ou como é chamado nesse idioma. A beleza da inferência de tipo é que é sempre opcional; você ainda pode especificar um tipo diferente se precisar de um. Quanto aoEntry
exemplo: bom argumento, vou substituí-lo porMap.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>>
. O Java nem sequer tem aliases de tipo :(colKey
é óbvio e irrelevante: nós apenas nos preocupamos em ser adequado como o segundo argumento dedoSomethingWith
. Se eu extraísse esse loop em uma função que produza um iterável de(key1, key2, value)
-triples, a assinatura mais geral seria<K1, K2, V> Iterable<TableEntry<K1, K2, V>> flattenTable(Map<K1, Map<K2, V>> table)
. Dentro dessa função, o tipo real decolKey
(Integer
, notK2
) é absolutamente irrelevante.View.OnClickListener listener = new View.OnClickListener()
. Você ainda saberia o tipo, mesmo que o programador fosse "preguiçoso" e o reduzisse paravar listener = new View.OnClickListener
(se isso fosse possível). Este tipo de redundância é comum - Não vou arriscar um palpite aqui - e removê-lo não resultam de pensar em futuros leitores. Todo recurso de idioma deve ser usado com cuidado, não estou questionando isso.É verdade que o código é lido com muito mais frequência do que está escrito. No entanto, a leitura também leva tempo, e duas telas de código são mais difíceis de navegar e ler do que uma tela de código; portanto, precisamos priorizar para compactar a melhor proporção de informações úteis / esforço de leitura. Este é um princípio geral de UX: Muita informação sobrecarrega e degrada a eficácia da interface.
E é minha experiência que , muitas vezes , o tipo exato não é (isso) importante. Certamente você às vezes expressões ninho:
x + y * z
,monkey.eat(bananas.get(i))
,factory.makeCar().drive()
. Cada uma delas contém subexpressões avaliadas para um valor cujo tipo não está gravado. No entanto, eles são perfeitamente claros. Estamos bem em deixar o tipo não declarado, porque é fácil descobrir o contexto e escrevê-lo faria mais mal do que bem (confundir a compreensão do fluxo de dados, ocupar tela valiosa e espaço de memória de curto prazo).Uma razão para não aninhar expressões como se não houvesse amanhã é que as linhas ficam longas e o fluxo de valores se torna incerto. A introdução de uma variável temporária ajuda nisso, impõe uma ordem e atribui um nome a um resultado parcial. No entanto, nem tudo o que se beneficia com esses aspectos também se beneficia de ter seu tipo explicitado:
Importa se
user
é um objeto de entidade, um número inteiro, uma string ou outra coisa? Para a maioria dos propósitos, não basta, basta saber que representa um usuário, vem da solicitação HTTP e é usado para buscar o nome a ser exibido no canto inferior direito da resposta.E quando se faz a matéria, o autor é livre para escrever o tipo. Essa é uma liberdade que deve ser usada com responsabilidade, mas o mesmo vale para tudo o que pode melhorar a legibilidade (nomes de variáveis e funções, formatação, design da API, espaço em branco). E, de fato, a convenção em Haskell e ML (onde tudo pode ser inferido sem esforço extra) é escrever os tipos de funções de funções não locais e também de variáveis e funções locais sempre que apropriado. Somente iniciantes permitem inferir todo tipo.
fonte
user
importa se você está tentando estender a função, porque determina o que você pode fazer com ouser
. Isso é importante se você deseja adicionar alguma verificação de integridade (devido a uma vulnerabilidade de segurança, por exemplo) ou se esqueceu que realmente precisa fazer algo com o usuário, além de apenas exibi-lo. É verdade que esses tipos de leitura para expansão são menos frequentes do que apenas ler o código, mas também são uma parte vital do nosso trabalho.Eu acho que a inferência de tipo é muito importante e deve ser suportada em qualquer linguagem moderna. Todos nós desenvolvemos IDEs e eles podem ajudar muito, caso você queira saber o tipo inferido, apenas alguns de nós invadimos
vi
. Pense em verbosidade e código de cerimônia em Java, por exemplo.Mas você pode dizer que está tudo bem, meu IDE vai me ajudar, pode ser um ponto válido. No entanto, alguns recursos não estariam lá sem a ajuda da inferência de tipo, tipos anônimos C #, por exemplo.
O Linq não seria tão bom como é agora sem a ajuda da inferência de tipo,
Select
por exemploEsse tipo anônimo será inferido ordenadamente na variável.
Não gosto de inferência de tipo em tipos de retorno,
Scala
porque acho que seu ponto se aplica aqui; deve ficar claro para nós o que uma função retorna para que possamos usar a API com mais fluênciafonte
Map<String,HashMap<String,String>>
? Certamente, se você não estiver usando tipos, soletrá-los traz pouco benefício.Table<User, File, String>
é mais informativo e há vantagens em escrevê-lo.Eu acho que a resposta para isso é realmente simples: economiza a leitura e a gravação de informações redundantes. Especialmente nas linguagens orientadas a objetos, nas quais você tem um tipo nos dois lados do sinal de igual.
O que também informa quando você deve ou não usá-lo - quando as informações não são redundantes.
fonte
Suponha que alguém veja o código:
Se
someBigLongGenericType
é atribuível a partir do tipo de retornosomeFactoryMethod
, qual a probabilidade de alguém ler o código perceber se os tipos não correspondem exatamente e com que facilidade alguém que notou a discrepância pode reconhecer se foi intencional ou não?Ao permitir a inferência, um idioma pode sugerir a alguém que está lendo o código que, quando o tipo de uma variável é declarado explicitamente, a pessoa deve tentar encontrar um motivo para isso. Isso, por sua vez, permite que as pessoas que estão lendo o código concentrem melhor seus esforços. Se, por outro lado, na grande maioria das vezes em que um tipo é especificado, ele é exatamente o mesmo que seria inferido, alguém que está lendo um código pode ser menos propenso a perceber os momentos em que é sutilmente diferente .
fonte
Vejo que já existem várias respostas excelentes. Algumas das quais repetirei, mas às vezes você só quer colocar as coisas com suas próprias palavras. Vou comentar com alguns exemplos do C ++, porque essa é a linguagem com a qual tenho mais familiaridade.
O que é necessário nunca é imprudente. A inferência de tipo é necessária para tornar outros recursos de linguagem práticos. Em C ++, é possível ter tipos indizíveis.
O C ++ 11 adicionou lambdas que também são indizíveis.
A inferência de tipo também sustenta modelos.
Mas suas perguntas foram "por que eu, o programador, gostaria de inferir o tipo de minhas variáveis quando leio o código? Não é mais rápido para alguém apenas ler o tipo do que pensar que tipo existe?"
A inferência de tipo remove a redundância. Quando se trata de ler código, às vezes pode ser mais rápido e fácil ter informações redundantes no código, mas a redundância pode ofuscar as informações úteis . Por exemplo:
Não é preciso muita familiaridade com a biblioteca padrão para um programador de C ++ identificar que eu sou um iterador,
i = v.begin()
portanto a declaração de tipo explícita é de valor limitado. Por sua presença, oculta os detalhes mais importantes (como os quei
apontam para o início do vetor). A boa resposta de @amon fornece um exemplo ainda melhor de verbosidade que oculta detalhes importantes. Em contraste, o uso de inferência de tipo dá maior destaque aos detalhes importantes.Embora a leitura do código seja importante, não é suficiente, em algum momento você precisará parar de ler e começar a escrever um novo código. A redundância no código torna a modificação do código mais lenta e mais difícil. Por exemplo, digamos que tenho o seguinte fragmento de código:
No caso em que eu preciso alterar o tipo de valor do vetor para alterar o código para:
Nesse caso, tenho que modificar o código em dois lugares. Contraste com a inferência de tipo, onde o código original é:
E o código modificado:
Note que agora só preciso alterar uma linha de código. Extrapole isso para um programa grande e a inferência de tipo pode propagar as alterações para os tipos muito mais rapidamente do que com um editor.
A redundância no código cria a possibilidade de erros. Sempre que seu código depende de duas informações mantidas equivalentes, existe a possibilidade de erro. Por exemplo, há uma inconsistência entre os dois tipos nesta declaração que provavelmente não se destina:
A redundância torna a intenção mais difícil de discernir. Em alguns casos, a inferência de tipo pode ser mais fácil de ler e entender, porque é mais simples que a especificação de tipo explícita. Considere o fragmento de código:
No caso que
sq(x)
retorna umint
, não é óbvio sey
é umint
porque é o tipo de retornosq(x)
ou porque é adequado às instruções que são usadasy
. Se eu alterar outro código para quesq(x)
não retorne maisint
, é incerto a partir dessa linha apenas se o tipo dey
deve ser atualizado. Contraste com o mesmo código, mas usando inferência de tipo:Neste, a intenção é clara,
y
deve ser do mesmo tipo retornada porsq(x)
. Quando o código altera o tipo de retornosq(x)
, o tipo dey
alteração é correspondido automaticamente.Em C ++, existe uma segunda razão pela qual o exemplo acima é mais simples com inferência de tipo, a inferência de tipo não pode introduzir conversão implícita de tipo. Se o tipo de retorno de
sq(x)
não forint
, o compilador insere silenciosamente uma conversão implícita emint
. Se o tipo de retorno desq(x)
é um tipo complexo de tipo que defineoperator int()
, essa chamada de função oculta pode ser arbitrariamente complexa.fonte
typeof
é inutilizada pela linguagem. E isso é um déficit da própria linguagem que deve ser corrigido na minha opinião.