Sovraccarico funzioni? Sì o no [chiuso]

15

Sto sviluppando un linguaggio compilato staticamente e strongmente tipizzato, e sto rivisitando l'idea di includere l'overloading delle funzioni come funzionalità del linguaggio. Mi sono reso conto di essere un po 'di parte, provenendo principalmente da uno sfondo C[++|#] .

Quali sono gli argomenti più convincenti per e contro incluso l'overloading di funzioni in una lingua?

EDIT: c'è qualcuno che ha un'opinione opposta?

Bertrand Meyer (creatore di Eiffel nel 1985/1986) chiama il metodo per sovraccaricare questo: (source)

a vanity mechanism that brings nothing to the semantic power of an O-O language, but hampers readability and complicates everyone's task

Ora queste sono alcune generalizzazioni radicali, ma è un ragazzo intelligente, quindi penso che sia sicuro dire che potrebbe sostenerli se fosse necessario. Infatti, ha quasi avuto Brad Abrams (uno degli sviluppatori CLSv1) convinto che .NET non dovrebbe supportare l'overloading dei metodi. (source) Questa è una roba potente. Qualcuno può far luce sui suoi pensieri e se il suo punto di vista è ancora giustificato 25 anni dopo?

    
posta Note to self - think of a name 29.09.2010 - 17:37
fonte

10 risposte

24

L'overloading delle funzioni è assolutamente fondamentale per il codice modello di stile C ++. Se devo usare nomi di funzioni diversi per tipi diversi, non posso scrivere codice generico. Ciò eliminerebbe una parte ampia e molto utilizzata della libreria C ++ e gran parte delle funzionalità di C ++.

Di solito è presente nei nomi delle funzioni dei membri. A.foo() può chiamare una funzione completamente diversa da B.foo() , ma entrambe le funzioni sono denominate foo . È presente negli operatori, dato che + fa cose diverse se applicato a numeri interi ea virgola mobile, ed è spesso usato come operatore di concatenazione di stringhe. Sembra strano non permetterlo anche nelle normali funzioni.

Abilita l'uso di "multimetodi" in stile Lisp comune, in cui l'esatta funzione chiamata dipende da due tipi di dati. Se non hai programmato nel Common Lisp Object System, provalo prima di chiamarlo inutile. È vitale per i flussi C ++.

I / O senza sovraccarico di funzione (o funzioni variadiche, che sono peggiori) richiederebbero un numero di funzioni diverse, sia per stampare valori di tipi diversi sia per convertire valori di tipi diversi in un tipo comune (come String).

Senza sovraccaricare le funzioni, se cambio il tipo di qualche variabile o valore ho bisogno di cambiare ogni funzione che lo usa. Rende molto più difficile il codice refactor.

Semplifica l'utilizzo delle API quando l'utente non deve ricordare quale convenzione di denominazione dei tipi è in uso e l'utente può semplicemente ricordare i nomi delle funzioni standard.

Senza l'overloading dell'operatore, dovremmo etichettare ciascuna funzione con i tipi che usa, se l'operazione base può essere usata su più di un tipo. Questa è essenzialmente la notazione ungherese, il modo sbagliato di farlo.

Nel complesso, rende un linguaggio molto più utilizzabile.

    
risposta data 29.09.2010 - 18:53
fonte
8

Consiglio almeno di essere a conoscenza delle classi di tipi in Haskell. Le classi di tipi sono state create per essere un approccio disciplinato all'overloading dell'operatore, ma hanno trovato altri usi e, in una certa misura, hanno reso Haskell quello che è.

Ad esempio, ecco un esempio di overload ad-hoc (Haskell non abbastanza valido):

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

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

Ed ecco lo stesso esempio di sovraccarico con le classi di tipi:

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  = ...

Uno svantaggio di questo è che devi trovare nomi funky per tutti i tuoi typeclass (come in Haskell, hai piuttosto il Monad , Functor , Applicative e il più semplice e più riconoscibile Eq , Num e Ord ).

Un aspetto positivo è che, una volta acquisita familiarità con una classe di caratteri, sai come utilizzare qualsiasi tipo in quella classe. Inoltre, è facile proteggere le funzioni da tipi che non implementano le classi necessarie, come in:

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

Modifica: in Haskell, se si desidera un operatore == che accetti due tipi diversi, è possibile utilizzare una classe di tipo a più parametri:

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  = ...

Naturalmente, questa è probabilmente una cattiva idea, dal momento che ti consente esplicitamente di confrontare mele e arance. Tuttavia, potresti prendere in considerazione questo per + , dal momento che aggiungere Word8 a Int è davvero una cosa sensata da fare in alcuni contesti.

    
risposta data 29.09.2010 - 19:07
fonte
4

Permetti l'overloading delle funzioni, non puoi fare quanto segue con i parametri opzionali (o se puoi, non molto bene).

l'esempio banale non assume alcun base.ToString() metodo

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...
    
risposta data 29.09.2010 - 18:46
fonte
2

Ho sempre preferito i parametri predefiniti rispetto all'overload delle funzioni. Le funzioni sovraccaricate in genere chiamano solo una versione "predefinita" con parametri predefiniti. Perché scrivere

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

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

Quando potrei fare:

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

Detto ciò, mi rendo conto che a volte le funzioni sovraccariche fanno cose diverse, piuttosto che chiamare semplicemente un'altra variante con parametri predefiniti ... ma in tal caso, non è una cattiva idea (infatti, è probabilmente un buono idea) per dargli un nome diverso.

(Inoltre, gli argomenti delle parole chiave in stile Python funzionano molto bene con i parametri predefiniti.)

    
risposta data 29.09.2010 - 18:09
fonte
2

Hai appena descritto Java. Oppure C #.

Perché stai reinventando la ruota?

Assicurati che il tipo di ritorno faccia parte della firma del metodo e Overload per i tuoi contenuti di cuori, in realtà pulisce il codice quando non devi dirlo.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }
    
risposta data 29.09.2010 - 17:56
fonte
2

Grrr .. non abbastanza privilegio per commentare ancora ..

@Mason Wheeler: sii consapevole quindi di Ada, che esegue il sovraccarico sul tipo restituito. Anche il mio linguaggio Felix lo fa anche in alcuni contesti, in particolare, quando una funzione restituisce un'altra funzione e c'è una chiamata del tipo:

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

il tipo di b può essere usato per la risoluzione di sovraccarico. Anche sovraccarichi C ++ sul tipo restituito in alcune circostanze:

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

In effetti ci sono algoritmi per l'overloading sul tipo restituito, usando l'inferenza di tipo. In realtà non è così difficile da fare con una macchina, il problema è che gli umani lo trovano difficile. (Penso che lo schema sia dato nel Libro del Drago, l'algoritmo è chiamato l'algoritmo altalenante se non ricordo male)

    
risposta data 05.12.2010 - 00:08
fonte
2

Caso d'uso contro l'overloading di funzioni: 25 metodi con nomi simili che fanno le stesse cose ma con insiemi di argomenti completamente diversi in un'ampia varietà di modelli.

Caso d'uso contro non implementare l'overloading di funzioni: 5 metodi con nomi simili con insiemi molto simili di tipi nello stesso identico modello.

Alla fine della giornata, non vedo l'ora di leggere i documenti per un'API prodotta in entrambi i casi.

Ma in un caso si tratta di ciò che gli utenti potrebbero fare. Nell'altro caso è ciò che gli utenti devono fare a causa di una limitazione della lingua. IMO, è meglio permettere almeno che gli autori dei programmi siano abbastanza intelligenti da sovraccaricarsi sensibilmente senza creare ambiguità. Quando schiaffi le mani e togli l'opzione, stai sostanzialmente garantendo che l'ambiguità accada. Sono più fiducioso che l'utente faccia la cosa giusta piuttosto che assumere che farà sempre la cosa sbagliata. Secondo la mia esperienza, il protezionismo tende a comportare comportamenti ancora peggiori da parte della comunità di una lingua.

    
risposta data 20.01.2013 - 17:25
fonte
1

Ho scelto di fornire normali classi di tipi di overload e multi-type nella mia lingua Felix.

Considero essenziale il sovraccarico (aperto), specialmente in un linguaggio che ha molti tipi numerici (Felix ha tutti i tipi numerici di C). Tuttavia a differenza del C ++ che abusa del sovraccarico facendo dipendere da esso i template, il polimorfismo di Felix è parametrico: è necessario un sovraccarico per i template in C ++ perché i template in C ++ sono mal progettati.

Anche le classi di tipi sono fornite in Felix. Per quelli che conoscono il C ++ ma non fanno il grok Haskell, ignorano quelli che lo descrivono come un sovraccarico. Non è da remoto come un sovraccarico, piuttosto è come la specializzazione del modello: dichiarate un modello che non implementate, quindi fornite le implementazioni per casi particolari come ne avete bisogno. La tipizzazione è parametrica- mente polimorfica, l'implementazione avviene mediante istanze ad hoc ma non è intesa come non vincolata: deve implementare la semantica prevista.

In Haskell (e C ++) non puoi dichiarare la semantica. In C ++ l'idea di "Concetti" è approssimativamente un tentativo di approssimare la semantica. In Felix, puoi approssimare l'intenzione con assiomi, riduzioni, lemmi e teoremi.

Il vantaggio principale, e solo dell'overloading (aperto) in un linguaggio ben definito come Felix è che rende più facile ricordare i nomi delle funzioni della libreria, sia per il programma che per il programma di revisione del codice .

Lo svantaggio principale del sovraccarico è il complesso algoritmo richiesto per implementarlo. Inoltre non si adatta molto bene all'inferenza di tipo: sebbene i due non siano del tutto esclusivi, l'algoritmo per fare entrambi è abbastanza complesso che il programmatore probabilmente non sarebbe in grado di prevedere i risultati.

In C ++ questo è anche un problema perché ha un algoritmo di corrispondenza sciatta e supporta anche conversioni di tipo automatiche: in Felix I "risolto" questo problema richiedendo una corrispondenza esatta e nessuna conversione di tipo automatico.

Quindi hai una scelta che penso: sovraccarico o inferenza del tipo. L'inferenza è carina, ma è anche molto difficile da implementare in modo da diagnosticare correttamente i conflitti. Ocaml, ad esempio, ti dice dove rileva un conflitto, ma non da dove ha inferito il tipo atteso.

Il sovraccarico non è molto migliore, anche se hai un compilatore di qualità che cerca di dirti tutti i candidati, può essere difficile leggere se i candidati sono polimorfici, e ancor peggio se è un hackery del modello C ++.

    
risposta data 26.11.2010 - 19:35
fonte
0

Arriva al contesto, ma penso che l'overloading renda una classe molto più utile quando ne sto usando una scritta da qualcun altro. Spesso si finisce con una minore ridondanza.

    
risposta data 29.09.2010 - 18:49
fonte
0

Se stai cercando di avere utenti con familiarità con le lingue della famiglia C, allora sì, perché i tuoi utenti se lo aspettano.

    
risposta data 29.09.2010 - 19:19
fonte

Leggi altre domande sui tag