ti-enxame.com

Sobrecarga de função? sim ou não

Estou desenvolvendo uma linguagem compilada estaticamente e fortemente tipada e estou revisitando a ideia de incluir a sobrecarga de função como um recurso de linguagem. Percebi que sou um pouco tendenciosa, proveniente principalmente de um C[++|#] fundo.

Quais são os argumentos mais convincentes para e contra incluindo sobrecarga de função 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: (origem)

um mecanismo de vaidade que não traz nada ao poder semântico de uma linguagem O-O, 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. (origem) Isso é algo poderoso. Alguém pode lançar alguma luz sobre seus pensamentos e se seu ponto de vista ainda se justifica 25 anos depois?

17

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, como + faz coisas diferentes quando aplicado 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 em funções regulares também.

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 se lembrar apenas dos nomes de função 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.

24
David Thornley

Eu recomendo pelo menos estar ciente das classes de tipo em Haskell. As classes de tipo foram criadas para ser 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  = ...

Uma desvantagem disso é que você precisa criar nomes descolados para todas as suas classes de tipo (como em Haskell, você tem os abstratos Monad, Functor, Applicative também como o mais simples e mais reconhecível Eq, Num e 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: Em Haskell, se você quisesse um == operador que aceita dois tipos diferentes, você pode 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á ideia, pois permite explicitamente comparar maçãs e laranjas. No entanto, você pode considerar isso para +, desde a adição de um Word8 para um Int realmente é uma coisa sensata a se fazer em alguns contextos.

8
Joey Adams

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 assume que nãobase.ToString()method

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...
4
Binary Worrier

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

Por que você está reinventando a roda?

Verifique se o tipo de retorno faz parte da assinatura do método e Sobrecarregar 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)
{ ... }
2
Josh K

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 chamar outra variante com parâmetros padrão ... mas, nesse caso, não é uma má idéia (na verdade, provavelmente é um bom idéia) apenas para dar um nome diferente.

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

2
mipadi

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 nos dois 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 nas mãos deles e tira a opção, basicamente garante que a ambiguidade vai 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.

2
Erik Reppen

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

@ Wheeler Mason: Esteja ciente de 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)

2
Yttrill

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.

No 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 é divertida, 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++.

1
Yttrill

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.

0
Morgan Herlocker

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

0
Conrad Frix