Por que mais idiomas tipicamente estatísticos não suportam sobrecarga de função / método por tipo de retorno? Não consigo pensar em nada disso. Parece não menos útil ou razoável do que suportar sobrecarga por tipo de parâmetro. Como é que é tão menos popular?
252
Respostas:
Ao contrário do que os outros estão dizendo, a sobrecarga por tipo de retorno é possível e é feita por alguns idiomas modernos. A objeção usual é que em código como
você não pode dizer qual
func()
está sendo chamado. Isso pode ser resolvido de algumas maneiras:int main() { (string)func(); }
.Dois dos idiomas que eu regularmente ( ab ) usam sobrecarga por tipo de retorno: Perl e Haskell . Deixe-me descrever o que eles fazem.
No Perl , existe uma distinção fundamental entre o contexto escalar e a lista (e outros, mas vamos fingir que existem dois). Toda função interna do Perl pode fazer coisas diferentes, dependendo do contexto em que é chamada. Por exemplo, o
join
operador força o contexto da lista (na coisa que está sendo unida) enquanto oscalar
operador força o contexto escalar, então compare:Todo operador no Perl faz algo no contexto escalar e algo no contexto da lista, e eles podem ser diferentes, como ilustrado. (Isso não é apenas para operadores aleatórios, como
localtime
. Se você usa uma matriz@a
no contexto de lista, ela retorna a matriz, enquanto no contexto escalar, retorna o número de elementos. Por exemplo,print @a
imprime os elementos, enquantoprint 0+@a
imprime o tamanho. ) Além disso, todo operador pode forçar um contexto, por exemplo, a adição+
força o contexto escalar. Toda entrada emman perlfunc
documentos isso. Por exemplo, aqui faz parte da entrada paraglob EXPR
:Agora, qual é a relação entre lista e contexto escalar? Bem
man perlfunc
dizportanto, não é uma simples questão de ter uma única função, e você faz uma conversão simples no final. De fato, escolhi o
localtime
exemplo por esse motivo.Não são apenas os integrados que têm esse comportamento. Qualquer usuário pode definir essa função usando
wantarray
, o que permite distinguir entre lista, escalar e contexto nulo. Assim, por exemplo, você pode decidir não fazer nada se estiver sendo chamado em um contexto nulo.Agora, você pode reclamar que isso não é uma sobrecarga verdadeira por valor de retorno, porque você tem apenas uma função, a qual é informada no contexto em que é chamada e, em seguida, age sobre essas informações. No entanto, isso é claramente equivalente (e análogo a como o Perl não permite literalmente a sobrecarga usual, mas uma função pode apenas examinar seus argumentos). Além disso, resolve bem a situação ambígua mencionada no início desta resposta. Perl não reclama que não sabe qual método chamar; apenas chama isso. Tudo o que precisa fazer é descobrir em que contexto a função foi chamada, o que é sempre possível:
(Nota: às vezes posso dizer operador Perl quando quero dizer função. Isso não é crucial para esta discussão.)
Haskell adota a outra abordagem, a saber, para não ter efeitos colaterais. Ele também possui um sistema de tipos robusto e, portanto, você pode escrever um código como o seguinte:
Esse código lê um número de ponto flutuante da entrada padrão e imprime sua raiz quadrada. Mas o que é surpreendente nisso? Bem, o tipo de
readLn
éreadLn :: Read a => IO a
. O que isso significa é que, para qualquer tipo que possa serRead
(formalmente, todo tipo que seja uma instância daRead
classe type),readLn
pode lê-lo. Como Haskell sabia que eu queria ler um número de ponto flutuante? Bem, o tipo desqrt
ésqrt :: Floating a => a -> a
, o que significa essencialmente quesqrt
só pode aceitar números de ponto flutuante como entradas e, portanto, Haskell inferiu o que eu queria.O que acontece quando Haskell não pode inferir o que eu quero? Bem, existem algumas possibilidades. Se eu não usar o valor de retorno, Haskell simplesmente não chamará a função em primeiro lugar. No entanto, se eu fazer usar o valor de retorno, então Haskell vai reclamar que não pode inferir o tipo:
Eu posso resolver a ambiguidade especificando o tipo que eu quero:
De qualquer forma, o que toda essa discussão significa é que a sobrecarga pelo valor de retorno é possível e está concluída, o que responde a parte da sua pergunta.
A outra parte da sua pergunta é por que mais idiomas não fazem isso. Vou deixar que outros respondam isso. No entanto, alguns comentários: a principal razão é provavelmente que a oportunidade de confusão é realmente maior aqui do que a sobrecarga por tipo de argumento. Você também pode examinar as justificativas de idiomas individuais:
Ada : "Pode parecer que a regra mais simples de resolução de sobrecarga é usar tudo - todas as informações de um contexto tão amplo quanto possível - para resolver a referência sobrecarregada. Essa regra pode ser simples, mas não é útil. Requer o leitor humano digitalizar pedaços de texto arbitrariamente grandes e fazer inferências arbitrariamente complexas (como (g) acima). Acreditamos que uma regra melhor é aquela que torna explícita a tarefa que um leitor ou compilador humano deve executar e que a torna o mais natural possível para o leitor humano ".
C ++ (subseção 7.4.1 da "Linguagem de programação C ++" de Bjarne Stroustrup): "Os tipos de retorno não são considerados na resolução de sobrecarga. O motivo é manter a resolução para um operador individual ou uma chamada de função independente do contexto. Considere:
Se o tipo de retorno fosse levado em consideração, não seria mais possível examinar uma chamada
sqrt()
isoladamente e determinar qual função foi chamada. "(Observe, para comparação, que em Haskell não há conversões implícitas ).Java ( Java Language Specification 9.4.1 ): "Um dos métodos herdados deve ser substituível pelo tipo de retorno para todos os outros métodos herdados; caso contrário, ocorrerá um erro em tempo de compilação." (Sim, eu sei que isso não dá uma justificativa. Tenho certeza de que Gosling é dada na "linguagem de programação Java". Talvez alguém tenha uma cópia? Aposto que é o "princípio da menor surpresa" em essência. ) No entanto, um fato curioso sobre Java: a JVM permite sobrecarga por valor de retorno! Isso é usado, por exemplo, no Scala , e também pode ser acessado diretamente por Java , brincando com os internos.
PS. Como nota final, é realmente possível sobrecarregar pelo valor de retorno em C ++ com um truque. Testemunha:
fonte
Foo
eBar
conversão bidirecional suporte e um método usa o tipoFoo
internamente, mas tipo de retornosBar
. Se esse método for chamado pelo código que forçará imediatamente o resultado a ser digitadoFoo
, o uso doBar
tipo de retorno poderá funcionar, mas oFoo
melhor seria. BTW, eu também gostaria de ver um meio através do qual ... #var someVar = someMethod();
(ou então designar que seu retorno não deve ser usado dessa maneira). Por exemplo, uma família de tipos que implementa uma interface Fluent pode se beneficiar de ter versões mutáveis e imutáveis, paravar thing2 = thing1.WithX(3).WithY(5).WithZ(9);
queWithX(3)
copiemthing1
para um objeto mutável, mutem X e retornem esse objeto mutável;WithY(5)
mudaria Y e retornaria o mesmo objeto; da mesma forma `WithZ (9). Em seguida, a atribuição seria convertida em um tipo imutável.Se as funções foram sobrecarregadas pelo tipo de retorno e você teve essas duas sobrecargas
não há como o compilador descobrir qual dessas duas funções chamar ao ver uma chamada como esta
Por esse motivo, os designers de idiomas geralmente não permitem a sobrecarga de valor de retorno.
Algumas linguagens (como MSIL), no entanto, não permitem a sobrecarga por tipo de retorno. Eles também enfrentam a dificuldade acima, é claro, mas têm soluções alternativas, para as quais você terá que consultar a documentação deles.
fonte
Nesse idioma, como você resolveria o seguinte:
se
f
tinha sobrecargasvoid f(int)
evoid f(string)
eg
teve sobrecargasint g(int)
estring g(int)
? Você precisaria de algum tipo de desambiguação.Eu acho que as situações em que você pode precisar disso seriam melhor atendidas escolhendo um novo nome para a função.
fonte
Para roubar uma resposta específica do C ++ de outra pergunta muito semelhante (dupe?):
Os tipos de retorno de função não entram em jogo na resolução de sobrecarga simplesmente porque Stroustrup (suponho que com a entrada de outros arquitetos C ++) queria que a resolução de sobrecarga fosse 'independente do contexto'. Veja 7.4.1 - "Tipo de Sobrecarga e Retorno" da "Linguagem de Programação C ++, Terceira Edição".
Eles queriam que se baseasse apenas em como a sobrecarga foi chamada - não em como o resultado foi usado (se foi usado). De fato, muitas funções são chamadas sem usar o resultado ou o resultado seria usado como parte de uma expressão maior. Um fator que tenho certeza de que entrou em jogo quando eles decidiram isso foi que, se o tipo de retorno fizesse parte da resolução, haveria muitas chamadas para funções sobrecarregadas que precisariam ser resolvidas com regras complexas ou teriam que ser executadas pelo compilador um erro que a chamada era ambígua.
E, Deus sabe, a resolução de sobrecarga em C ++ é complexa o suficiente ...
fonte
No haskell, é possível mesmo que não tenha sobrecarga de função. Haskell usa classes de tipos. Em um programa você pode ver:
A sobrecarga de funções em si não é tão popular. A maioria das linguagens que já vi são C ++, talvez java e / ou C #. Em todas as linguagens dinâmicas, é uma abreviação para:
Portanto, não há muito sentido nisso. A maioria das pessoas não está interessada em saber se o idioma pode ajudá-lo a eliminar uma única linha por onde quer que você o use.
A correspondência de padrões é um pouco semelhante à sobrecarga de funções, e acho que às vezes funciona da mesma forma. Porém, não é comum porque é útil apenas para alguns programas e é difícil de implementar na maioria dos idiomas.
Você vê que existem infinitamente outros recursos melhores e fáceis de implementar para implementar na linguagem, incluindo:
fonte
Boas respostas! A resposta de A.Rex, em particular, é muito detalhada e instrutiva. Como ele aponta, C ++ não considerar operadores de conversão de tipo fornecidos pelo usuário durante a compilação
lhs = func();
(onde func é realmente o nome de um struct) . Minha solução alternativa é um pouco diferente - não melhor, apenas diferente (embora seja baseada na mesma idéia básica).Considerando que eu queria escrever ...
Acabei com uma solução que usa uma estrutura parametrizada (com T = o tipo de retorno):
Um benefício dessa solução é que qualquer código que inclua essas definições de modelo possa adicionar mais especializações para mais tipos. Além disso, você pode fazer especializações parciais da estrutura, conforme necessário. Por exemplo, se você quisesse tratamento especial para tipos de ponteiros:
Como negativo, você não pode escrever
int x = func();
com a minha solução. Você tem que escreverint x = func<int>();
. Você precisa dizer explicitamente qual é o tipo de retorno, em vez de solicitar ao compilador que analise isso examinando os operadores de conversão de tipo. Eu diria que a "minha" solução e a da A.Rex pertencem a uma frente pareto-ideal de maneiras de resolver esse dilema do C ++ :)fonte
se você deseja sobrecarregar métodos com diferentes tipos de retorno, basta adicionar um parâmetro fictício com valor padrão para permitir a execução de sobrecarga, mas não esqueça que o tipo de parâmetro deve ser diferente, portanto a lógica de sobrecarga funciona a seguir é um exemplo no delphi:
use-o assim
fonte
Como já mostrado - chamadas ambíguas de uma função que difere apenas pelo tipo de retorno introduz ambiguidade. A ambiguidade induz código defeituoso. Código defeituoso deve ser evitado.
A complexidade impulsionada pela tentativa de ambiguidade mostra que esse não é um bom truque. Além de um exercício intelectual - por que não usar procedimentos com parâmetros de referência.
fonte
doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
esse recurso de sobrecarga não é difícil de gerenciar, se você o observar de uma maneira um pouco diferente. considere o seguinte,
se um idioma retornasse sobrecarga, isso permitiria sobrecarregar parâmetros, mas não duplicações. isso resolveria o problema de:
porque existe apenas um f (int choice) para escolher.
fonte
No .NET, às vezes usamos um parâmetro para indicar a saída desejada de um resultado genérico e, em seguida, fazemos uma conversão para obter o que esperamos.
C #
Talvez este exemplo possa ajudar também:
C ++
fonte
Para o registro, o Octave permite resultados diferentes de acordo com o elemento de retorno ser escalar versus matriz.
Cf também Decomposição de Valor Singular .
fonte
Este é um pouco diferente para C ++; Não sei se isso seria considerado sobrecarregado por tipo de retorno diretamente. É mais uma especialização de modelo que atua da maneira de.
util.h
util.inl
util.cpp
Este exemplo não está exatamente usando a resolução de sobrecarga de função por tipo de retorno, no entanto, essa classe não-objeto c ++ está usando a especialização de modelo para simular a resolução de sobrecarga de função por tipo de retorno com um método estático privado.
Cada uma das
convertToType
funções está chamando o modelo de funçãostringToValue()
e, se você observar os detalhes da implementação ou o algoritmo desse modelo de função, está chamandogetValue<T>( param, param )
e retornando um tipoT
e armazenando-o em umT*
que é passado para ostringToValue()
modelo de função como um de seus parâmetros. .Diferente de algo assim; O C ++ realmente não possui um mecanismo para ter a função sobrecarregando a resolução por tipo de retorno. Pode haver outras construções ou mecanismos que não conheço que possam simular resolução por tipo de retorno.
fonte
Eu acho que esse é um GAP na definição moderna de C ++ ... por quê?
Por que um compilador C ++ não pode gerar um erro no exemplo "3" e aceitar o código no exemplo "1 + 2"?
fonte
A maioria das linguagens estáticas agora também oferece suporte a genéricos, o que resolveria seu problema. Como afirmado anteriormente, sem ter diferenças de parâmetro, não há como saber qual chamar. Portanto, se você quiser fazer isso, basta usar genéricos e encerrar o dia.
fonte