Sobrecarga de função? Sim ou não [fechado]

16

Estou desenvolvendo uma linguagem compilada de forma estática e fortemente tipada e estou revisitando a ideia de incluir sobrecarga de função como um recurso de linguagem. Percebi que sou um pouco tendencioso, vindo principalmente de um C[++|#]plano de fundo.

Quais são as mais convincentes argumentos para e contra , incluindo a função de sobrecarga em um idioma?


EDIT: Não há ninguém que tenha uma opinião contrária?

Bertrand Meyer (criador da Eiffel em 1985/1986) chama o método de sobrecarregar isso: (fonte)

um mecanismo de vaidade que não traz nada ao poder semântico de uma linguagem OO, mas dificulta a legibilidade e complica a tarefa de todos

Agora, essas são algumas generalizações abrangentes, mas ele é um cara inteligente, então acho que é seguro dizer que ele poderia apoiá-las, se necessário. Na verdade, ele quase convencera Brad Abrams (um dos desenvolvedores do CLSv1) de que o .NET não deveria suportar a sobrecarga de métodos. (fonte) Isso é algo poderoso. Alguém pode lançar alguma luz sobre seus pensamentos e se seu ponto de vista ainda é justificado 25 anos depois?

Nota para pensar em um nome
fonte

Respostas:

24

A sobrecarga de funções é absolutamente crítica para o código de modelo no estilo C ++. Se eu tiver que usar nomes de funções diferentes para tipos diferentes, não consigo escrever código genérico. Isso eliminaria uma parte grande e muito usada da biblioteca C ++ e grande parte da funcionalidade do C ++.

Geralmente está presente nos nomes das funções de membro. A.foo()pode chamar uma função totalmente diferente de B.foo(), mas ambas as funções são nomeadas foo. Está presente nos operadores, assim como +coisas diferentes quando aplicadas a números inteiros e de ponto flutuante, e é frequentemente usado como um operador de concatenação de cadeias. Parece estranho não permitir isso também em funções regulares.

Permite o uso de "multimétodos" comuns no estilo Lisp, nos quais a função exata chamada depende de dois tipos de dados. Se você não programou no Common Lisp Object System, tente antes de chamar isso de inútil. É vital para fluxos C ++.

A E / S sem sobrecarga de função (ou funções variáveis, que são piores) exigiria várias funções diferentes, para imprimir valores de tipos diferentes ou converter valores de tipos diferentes em um tipo comum (como String).

Sem sobrecarga de função, se eu alterar o tipo de alguma variável ou valor, preciso alterar todas as funções que a usam. Isso torna muito mais difícil refatorar o código.

Isso facilita o uso de APIs quando o usuário não precisa se lembrar de qual convenção de nomenclatura de tipos está em uso, e o usuário pode apenas lembrar os nomes de funções padrão.

Sem sobrecarga do operador, teríamos que rotular cada função com os tipos que ela usa, se essa operação básica puder ser usada em mais de um tipo. Esta é essencialmente a notação húngara, a má maneira de fazê-lo.

No geral, torna um idioma muito mais utilizável.

David Thornley
fonte
1
+1, todos os pontos muito bons. E confie em mim, não acho que os métodos múltiplos sejam inúteis ... Amaldiçoo o próprio teclado que digito toda vez que sou forçado a usar o padrão de visitante.
Note to self - pense em um nome
Esse cara não está descrevendo a função substituindo e não sobrecarregando?
dragosb
8

Eu recomendo pelo menos estar ciente das classes de tipo no Haskell. As classes de tipo foram criadas para serem uma abordagem disciplinada à sobrecarga de operadores, mas descobriram outros usos e, até certo ponto, fizeram Haskell o que é.

Por exemplo, aqui está um exemplo de sobrecarga ad-hoc (Haskell não muito válido):

(==) :: Int -> Int -> Bool
x == y = ...
x /= y = not (x == y)

(==) :: Char -> Char -> Bool
x == y = ...
x /= y = not (x == y)

E aqui está o mesmo exemplo de sobrecarga com classes de tipo:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

    x /= y  =  not (x == y)

instance Eq Int where
    x == y  = ...

instance Eq Char where
    x == y  = ...

A desvantagem desta situação é que você tem que vir acima com nomes funky para todos os seus typeclasses (como em Haskell, você tem o bastante abstrato Monad, Functor, Applicativebem como o mais simples e mais reconhecível Eq, Nume Ord).

Uma vantagem é que, quando você estiver familiarizado com uma classe, você sabe como usar qualquer tipo nessa classe. Além disso, é fácil proteger funções de tipos que não implementam as classes necessárias, como em:

group :: (Eq a) => [a] -> [[a]]
group = groupBy (==)

Edit: No Haskell, se você quiser um ==operador que aceite dois tipos diferentes, poderá usar uma classe de tipo com vários parâmetros:

class Eq a b where
    (==) :: a -> b -> Bool
    (/=) :: a -> b -> Bool

    x /= y  =  not (x == y)

instance Eq Int Int where
    x == y  = ...

instance Eq Char Char where
    x == y  = ...

instance Eq Int Float where
    x == y  = ...

Obviamente, essa é provavelmente uma má idéia, pois permite explicitamente comparar maçãs e laranjas. No entanto, convém considerar isso +, pois adicionar um Word8a Intrealmente é uma coisa sensata a ser feita em alguns contextos.

Joey Adams
fonte
+1, venho tentando entender esse conceito desde que o li pela primeira vez, e isso ajuda. Esse paradigma torna impossível definir, por exemplo, em (==) :: Int -> Float -> Boolqualquer lugar? (independentemente de ser uma boa ideia, é claro)
Observação: pense em um nome.
Se você permitir classes do tipo multiparâmetros (suportadas pelo Haskell como uma extensão), poderá. Eu atualizei a resposta com um exemplo.
Joey Adams
Hmm interessante. Então, basicamente, class Eq a ...seria traduzido para a pseudo-família C interface Eq<A> {bool operator==(A x, A y);}e, em vez de usar código modelado para comparar objetos arbitrários, você usa essa 'interface'. Isso está certo?
Note to self - pense em um nome
Certo. Você também pode dar uma olhada rápida nas interfaces no Go. Ou seja, você não precisa declarar um tipo como implementando uma interface, apenas implementando todos os métodos para essa interface.
Joey Adams
1
@ Notetoself-thinkofaname: Em relação a operadores adicionais - sim e não. Ele permite ==estar em um espaço de nome diferente, mas não permite substituí-lo. Observe que há um único espaço para nome ( Prelude) incluído por padrão, mas você pode impedir o carregamento usando extensões ou importando-o explicitamente ( import Prelude ()não importará nada Preludee import qualified Prelude as Pnão inserirá símbolos no espaço para nome atual).
Maciej Piechotka
4

Permitir sobrecarga de função, você não pode fazer o seguinte com parâmetros opcionais (ou, se puder, não muito bem).

exemplo trivial não assume nenhum base.ToString() método

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...
Preocupação binária
fonte
Para um idioma fortemente tipado, sim. Para uma linguagem de tipo fraco, não. Você pode fazer o acima com apenas uma função em um idioma de tipo fraco.
Spex
2

Eu sempre preferi parâmetros padrão em vez de sobrecarga de função. Funções sobrecarregadas geralmente chamam uma versão "padrão" com parâmetros padrão. Por que escrever

int indexOf(char ch)
{
  return self.indexOf(ch, 0);
}

int indexOf(char ch, int fromIndex)
{
  // Do whatever
}

Quando eu poderia fazer:

int indexOf(char ch, int fromIndex=0)
{
  // Do whatever
}

Dito isso, percebo que, às vezes, funções sobrecarregadas fazem coisas diferentes, em vez de apenas chamar outra variante com parâmetros padrão ... mas, nesse caso, não é uma má idéia (de fato, provavelmente é uma boa idéia) apenas dar uma nome diferente.

(Além disso, os argumentos de palavra-chave no estilo Python funcionam muito bem com os parâmetros padrão.)

mipadi
fonte
Ok, vamos tentar de novo, e vou tentar fazer sentido dessa vez ... E Array Slice(int start, int length) {...}sobrecarregado Array Slice(int start) {return this.Slice(start, this.Count - start);}? Isso não pode ser codificado usando parâmetros padrão. Você acha que eles deveriam receber nomes diferentes? Se sim, como você os nomearia?
Note a self - pense em um nome
Isso não aborda todos os usos de sobrecarga.
MetalMikester
@MetalMikester: Quais usos você sente que eu perdi?
Mipadi
@mipadi Falta coisas como indexOf(char ch)+ indexOf(Date dt)em uma lista. Também gosto de valores padrão, mas eles não são intercambiáveis ​​com a digitação estática.
Mark
2

Você acabou de descrever o Java. Ou c #.

Por que você está reinventando a roda?

Certifique-se de que o tipo de retorno faça parte da assinatura do método e sobrecarregue o conteúdo do seu coração; ele realmente limpa o código quando você não precisa dizer.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }
Josh K
fonte
7
Há uma razão pela qual o tipo de retorno não faz parte da assinatura do método - ou pelo menos não é uma parte que pode ser usada para resolução de sobrecarga - em qualquer idioma que eu conheça. Quando você chama uma função como procedimento, sem atribuir o resultado a uma variável ou propriedade, como o compilador deve descobrir qual versão chamar, se todos os outros argumentos forem idênticos?
Mason Wheeler
@Mason: Você pode detectar heuristicamente o tipo de retorno esperado com base no que se espera que seja devolvido, no entanto, não espero que isso seja feito.
21910 Josh K
1
Umm ... como sua heurística sabe o que se espera que seja retornado quando você não espera nenhum valor de retorno?
Mason Wheeler
1
A heurística de expectativa de tipo realmente está em vigor ... você pode fazer coisas assim EnumX.Flag1 | Flag2 | Flag3. Eu não vou implementar isso, no entanto. Se eu fiz e o tipo de retorno não foi usado, procuraria um tipo de retorno de void.
Note to self - pense em um nome
1
@Mason: Essa é uma boa pergunta, mas nesse caso eu procuraria uma função nula (como mencionado). Também em teoria, você pode escolher qualquer um deles, porque todos executam a mesma função; basta retornar os dados em um formato diferente.
Josh K
2

Grrr .. não é privilégio suficiente para comentar ainda ..

@ Wheeler Mason: Esteja ciente do Ada, que sobrecarrega no tipo de retorno. Também minha linguagem Felix faz isso em alguns contextos, especificamente, quando uma função retorna outra função e há uma chamada como:

f a b  // application is left assoc: (f a) b

o tipo de b pode ser usado para resolução de sobrecarga. Também o C ++ sobrecarrega no tipo de retorno em algumas circunstâncias:

int (*f)(int) = g; // choses g based on type, not just signature

De fato, existem algoritmos para sobrecarga no tipo de retorno, usando inferência de tipo. Na verdade, não é tão difícil de fazer com uma máquina, o problema é que os humanos acham difícil. (Eu acho que o esboço é dado no Dragon Book, o algoritmo é chamado de algoritmo de gangorra, se bem me lembro)

Yttrill
fonte
2

Caso de uso contra a implementação de sobrecarga de função: 25 métodos com nomes semelhantes que meio que fazem as mesmas coisas, mas com conjuntos de argumentos completamente diferentes em uma ampla variedade de padrões.

Caso de uso contra a não sobrecarga de funções de implementação: 5 métodos com nomes semelhantes com conjuntos de tipos muito semelhantes no mesmo padrão.

No final do dia, não estou ansioso para ler os documentos de uma API produzida em ambos os casos.

Mas, em um caso, é sobre o que os usuários podem fazer. No outro caso, é o que os usuários devem fazer devido a uma restrição de idioma. Na IMO, é melhor pelo menos permitir a possibilidade de os autores do programa serem inteligentes o suficiente para sobrecarregar sensatamente sem criar ambiguidade. Quando você dá um tapa em suas mãos e tira a opção, basicamente garante que a ambiguidade acontecerá. Estou mais confiando no usuário a fazer a coisa certa do que supor que ele sempre fará a coisa errada. Na minha experiência, o protecionismo tende a levar a comportamentos ainda piores por parte da comunidade de uma língua.

Erik Reppen
fonte
1

Eu escolhi fornecer sobrecarga comum e classes de tipo multi-tipo no meu idioma Felix.

Considero a sobrecarga (aberta) essencial, especialmente em um idioma que possui muitos tipos numéricos (Felix possui todos os tipos numéricos de C). No entanto, diferentemente do C ++, que abusa da sobrecarga ao fazer com que os modelos dependam dele, o polimorfismo Felix é paramétrico: você precisa sobrecarregar os modelos em C ++ porque os modelos em C ++ são mal projetados.

As classes de tipo também são fornecidas no Felix. Para aqueles que conhecem C ++, mas não criticam Haskell, ignore aqueles que o descrevem como sobrecarga. Não é remotamente como sobrecarregar, é como especialização de modelo: você declara um modelo que não implementa e fornece implementações para casos específicos, conforme necessário. A digitação é parametricamente polimórfica, a implementação é por instanciação ad hoc, mas não se destina a ser irrestrita: ela precisa implementar a semântica pretendida.

No Haskell (e C ++), você não pode declarar a semântica. Em C ++, a idéia "Conceitos" é aproximadamente uma tentativa de aproximar a semântica. Em Felix, você pode aproximar a intenção com axiomas, reduções, lemas e teoremas.

A principal e única vantagem da sobrecarga (aberta) em uma linguagem bem baseada em princípios como o Felix é que facilita a lembrança dos nomes das funções da biblioteca, tanto para o criador do programa quanto para o revisor de código.

A principal desvantagem da sobrecarga é o algoritmo complexo necessário para implementá-lo. Também não se encaixa muito bem com a inferência de tipo: embora os dois não sejam totalmente exclusivos, o algoritmo para fazer as duas coisas é complexo o suficiente, o programador provavelmente não seria capaz de prever os resultados.

Em C ++, isso também é um problema, pois possui um algoritmo de correspondência desleixada e também suporta conversões de tipo automáticas: no Felix, eu "consertei" esse problema exigindo uma correspondência exata e nenhuma conversão de tipos automática.

Então você tem uma escolha: sobrecarregar ou digitar inferência. A inferência é atraente, mas também é muito difícil de implementar de uma maneira que diagnostica adequadamente os conflitos. Ocaml, por exemplo, informa onde detecta um conflito, mas não de onde inferiu o tipo esperado.

A sobrecarga não é muito melhor, mesmo se você tiver um compilador de qualidade que tente informar todos os candidatos, pode ser difícil ler se os candidatos são polimórficos e, pior ainda, se for hackeamento de modelo C ++.

Yttrill
fonte
Soa interessante. Gostaria de ler mais, mas o link para os documentos na página Felix está quebrado.
Nota para auto - pense em um nome
Sim, o site inteiro está em construção (novamente), desculpe.
Yttrill
0

Tudo se resume ao contexto, mas acho que sobrecarregar torna uma classe muito mais útil quando estou usando uma escrita por outra pessoa. Você geralmente acaba com menos redundância.

Morgan Herlocker
fonte
0

Se você deseja ter usuários familiarizados com os idiomas da família C, sim, deve fazê-lo, porque os usuários o esperam.

Conrad Frix
fonte