Funktionsüberlastung? Ja oder Nein [geschlossen]

16

Ich entwickle eine statisch und stark typisierte, kompilierte Sprache und überlege mir, ob ich Funktionsüberladung als Sprachfeature einbeziehen soll. Mir wurde klar, dass ich ein bisschen voreingenommen bin, hauptsächlich aus dem C[++|#]Hintergrund.

Was sind die überzeugendsten Argumente für und gegen das Einbeziehen einer Funktionsüberladung in eine Sprache?


EDIT: Gibt es niemanden, der eine gegenteilige Meinung hat?

Bertrand Meyer (Schöpfer von Eiffel 1985/86) nennt eine Methode, die dies überlastet: (Quelle)

Ein Vanity-Mechanismus, der nichts zur semantischen Kraft einer OO-Sprache beiträgt, aber die Lesbarkeit beeinträchtigt und die Arbeit aller erschwert

Nun, das sind einige pauschale Verallgemeinerungen, aber er ist ein kluger Kerl, und ich denke, es ist sicher zu sagen, dass er sie unterstützen könnte, wenn er muss. Fast hätte er Brad Abrams (einen der CLSv1-Entwickler) davon überzeugt, dass .NET das Überladen von Methoden nicht unterstützen sollte. (Quelle) Das ist mächtiges Zeug. Kann irgendjemand etwas Licht in seine Gedanken bringen und ob sein Standpunkt nach 25 Jahren noch gerechtfertigt ist?

Notiz an mich selbst - denke an einen Namen
quelle

Antworten:

24

Das Überladen von Funktionen ist für C ++ - Vorlagencode absolut kritisch. Wenn ich unterschiedliche Funktionsnamen für unterschiedliche Typen verwenden muss, kann ich keinen generischen Code schreiben. Das würde einen großen und stark genutzten Teil der C ++ - Bibliothek und einen Großteil der C ++ - Funktionalität eliminieren.

Es ist normalerweise in Mitgliedsfunktionsnamen vorhanden. A.foo()kann eine ganz andere Funktion aufrufen B.foo(), aber beide Funktionen heißen foo. Es ist in Operatoren vorhanden, ebenso +wie verschiedene Dinge, wenn es auf Ganzzahlen und Gleitkommazahlen angewendet wird, und es wird häufig als Zeichenfolgenverkettungsoperator verwendet. Es scheint seltsam, es auch in regulären Funktionen nicht zuzulassen.

Es ermöglicht die Verwendung von Common Lisp-artigen "Multimethoden", bei denen die genaue aufgerufene Funktion von zwei Datentypen abhängt. Wenn Sie das Common Lisp Object System noch nicht programmiert haben, versuchen Sie es, bevor Sie es als unbrauchbar bezeichnen. Es ist wichtig für C ++ - Streams.

E / A ohne Funktionsüberladung (oder verschiedene Funktionen, die schlimmer sind) erfordern eine Reihe verschiedener Funktionen, um entweder Werte verschiedener Typen zu drucken oder Werte verschiedener Typen in einen gemeinsamen Typ (wie z. B. String) umzuwandeln.

Wenn ich den Typ einer Variablen oder eines Werts ändere, ohne die Funktion zu überladen, muss ich jede Funktion ändern, die sie verwendet. Es macht es viel schwieriger, Code umzugestalten.

Es erleichtert die Verwendung von APIs, wenn der Benutzer sich nicht erinnern muss, welche Typbenennungskonvention verwendet wird, und der Benutzer sich nur Standardfunktionsnamen merken kann.

Ohne das Überladen von Operatoren müssten wir jede Funktion mit den von ihr verwendeten Typen kennzeichnen, wenn diese Basisoperation für mehr als einen Typ verwendet werden kann. Dies ist im Wesentlichen die ungarische Notation, die schlechte Art, dies zu tun.

Insgesamt macht es eine Sprache weitaus benutzerfreundlicher.

David Thornley
quelle
1
+1, alles sehr gute Punkte. Und glauben Sie mir, ich denke nicht, dass Multimethoden nutzlos sind ... Ich verfluche genau die Tastatur, auf der ich tippe, jedes Mal, wenn ich gezwungen bin, das Besuchermuster zu verwenden.
Hinweis für sich selbst - denken Sie an einen Namen
Beschreibt dieser Typ nicht, dass Funktionen außer Kraft gesetzt und nicht überladen werden?
Dragosb
8

Ich empfehle, zumindest die Typenklassen in Haskell zu kennen. Typklassen wurden als disziplinierter Ansatz für das Überladen von Operatoren entwickelt, haben jedoch andere Verwendungszwecke gefunden und Haskell in gewissem Maße zu dem gemacht, was es ist.

Beispiel für eine Ad-hoc-Überladung (nicht ganz gültiges Haskell):

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

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

Und hier ist das gleiche Beispiel für das Überladen mit Typklassen:

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

Ein Nachteil davon ist , dass Sie mit funky Namen für alle Ihre typeclasses zu kommen haben (wie in Haskell, haben Sie die eher abstrakt Monad, Functor, Applicativesowie die einfachere und besser erkennbar Eq, Numund Ord).

Ein Vorteil ist, dass Sie, sobald Sie mit einer Typenklasse vertraut sind, wissen, wie Sie einen Typ in dieser Klasse verwenden. Außerdem ist es einfach, Funktionen vor Typen zu schützen, die die erforderlichen Klassen nicht implementieren, wie in:

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

Bearbeiten: Wenn Sie in Haskell einen ==Operator benötigen, der zwei verschiedene Typen akzeptiert, können Sie eine Typklasse mit mehreren Parametern verwenden:

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

Natürlich ist dies wahrscheinlich eine schlechte Idee, da Sie damit explizit Äpfel und Orangen vergleichen können. Sie sollten dies jedoch in Betracht ziehen +, da das Hinzufügen von a Word8zu Inta in manchen Kontexten sinnvoll ist.

Joey Adams
quelle
+1, ich habe versucht, dieses Konzept zu verstehen, seit ich es zum ersten Mal gelesen habe, und das hilft. Macht es dieses Paradigma unmöglich, zum Beispiel (==) :: Int -> Float -> Boolirgendwo zu definieren ? (Unabhängig davon, ob es eine gute Idee ist, natürlich)
Notiz an sich selbst - denken Sie an einen Namen
Wenn Sie Typklassen mit mehreren Parametern zulassen (die Haskell als Erweiterung unterstützt), können Sie dies tun. Ich habe die Antwort mit einem Beispiel aktualisiert.
Joey Adams
Hmm, interessant. Im Grunde genommen class Eq a ...wäre das also eine Übersetzung in eine Pseudo-C-Familie. interface Eq<A> {bool operator==(A x, A y);}Anstatt Vorlagencode zum Vergleichen beliebiger Objekte zu verwenden, verwenden Sie diese „Schnittstelle“. Ist das richtig?
Notiz
Richtig. Vielleicht möchten Sie auch einen kurzen Blick auf die Benutzeroberflächen in Go werfen. Sie müssen nämlich keinen Typ als Implementierung einer Schnittstelle deklarieren, sondern nur alle Methoden für diese Schnittstelle implementieren.
Joey Adams
1
@ Notetoself-thinkofaname: Bezüglich zusätzlicher Operatoren - ja und nein. Es erlaubt ==, sich in einem anderen Namensraum zu befinden, aber es erlaubt nicht, ihn zu überschreiben. Beachten Sie, dass Preludestandardmäßig ein einzelner Namespace ( ) enthalten ist. Sie können das Laden jedoch verhindern, indem Sie Erweiterungen verwenden oder ihn explizit importieren (es import Prelude ()wird nichts aus dem aktuellen Namespace importiert Preludeund import qualified Prelude as Pkeine Symbole in den aktuellen Namespace eingefügt).
Maciej Piechotka
4

Erlaube das Überladen von Funktionen, du kannst das Folgende nicht mit optionalen Parametern machen (oder wenn du kannst, nicht gut).

triviales Beispiel setzt keine Methode vorausbase.ToString()

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...
Binary Worrier
quelle
Für eine stark typisierte Sprache ja. Für eine schwach getippte Sprache, nein. Sie können dies mit nur einer Funktion in einer schwach getippten Sprache tun.
spex
2

Ich habe immer Standardparameter der Funktionsüberladung vorgezogen. Überladene Funktionen rufen im Allgemeinen nur eine "Standard" -Version mit Standardparametern auf. Warum schreiben?

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

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

Wann könnte ich tun:

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

Trotzdem stelle ich fest, dass manchmal überladene Funktionen andere Dinge tun, als nur eine andere Variante mit Standardparametern aufzurufen. Aber in einem solchen Fall ist es keine schlechte Idee (in der Tat ist es wahrscheinlich eine gute Idee), einfach eine zu geben anderer Name.

(Außerdem funktionieren Schlüsselwortargumente im Python-Stil sehr gut mit Standardparametern.)

mipadi
quelle
Okay, lass es uns noch einmal versuchen, und ich werde versuchen, diesmal einen Sinn zu ergeben ... Was ist mit Array Slice(int start, int length) {...}überladen Array Slice(int start) {return this.Slice(start, this.Count - start);}? Das kann nicht mit Standardparametern codiert werden. Denkst du, sie sollten unterschiedliche Namen bekommen? Wenn ja, wie würden Sie sie nennen?
Hinweis für sich selbst - denken Sie an einen Namen
Das betrifft nicht alle Verwendungszwecke von Überladung.
MetalMikester
@MetalMikester: Welchen Nutzen glaubst du, habe ich vermisst?
mipadi
@mipadi Es fehlen Dinge wie indexOf(char ch)+ indexOf(Date dt)auf einer Liste. Ich mag auch Standardwerte, aber sie sind nicht mit statischer Eingabe austauschbar.
Mark
2

Sie haben gerade Java beschrieben. Oder C #.

Warum erfindest du das Rad neu?

Vergewissern Sie sich, dass der Rückgabetyp Teil der Methodensignatur ist, und bereinigen Sie den Code wirklich, wenn Sie nichts sagen müssen.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }
Josh K
quelle
7
Es gibt einen Grund, warum der Rückgabetyp in keiner mir bekannten Sprache Teil der Methodensignatur ist - oder zumindest kein Teil, der für die Überlastungslösung verwendet werden kann. Wenn Sie eine Funktion als Prozedur aufrufen, ohne das Ergebnis einer Variablen oder Eigenschaft zuzuweisen, wie soll der Compiler dann herausfinden, welche Version aufzurufen ist, wenn alle anderen Argumente identisch sind?
Mason Wheeler
@Mason: Sie können den erwarteten Rückgabetyp anhand der erwarteten Rückgabetypen heuristisch ermitteln. Ich erwarte jedoch nicht, dass dies getan wird.
Josh K
1
Umm ... woher weiß Ihre Heuristik, was erwartet wird, wenn Sie keinen Rückgabewert erwarten?
Mason Wheeler
1
Die Heuristik der Typerwartung ist tatsächlich vorhanden ... Sie können Dinge tun wie EnumX.Flag1 | Flag2 | Flag3. Ich werde dies jedoch nicht implementieren. Wenn ich es täte und der Rückgabetyp nicht verwendet würde, würde ich nach einem Rückgabetyp von suchen void.
Hinweis für sich selbst - denken Sie an einen Namen
1
@Mason: Das ist eine gute Frage, aber in diesem Fall würde ich nach einer Void-Funktion suchen (wie erwähnt). Auch in der Theorie könnten Sie irgendwelche von ihnen wählen, weil sie alle die gleiche Funktion erfüllen, bringen Sie einfach Daten in einem anderen Format.
Josh K
2

Grrr .. nicht genug Privileg, um noch zu kommentieren ..

@Mason Wheeler: Achte dann auf Ada, die bei Rückgabetyp überlastet. Auch meine Sprache Felix macht das in einigen Kontexten, insbesondere wenn eine Funktion eine andere Funktion zurückgibt und ein Aufruf wie folgt erfolgt:

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

Der Typ b kann für die Überlastungsauflösung verwendet werden. Unter bestimmten Umständen auch C ++ - Überladungen beim Rückgabetyp:

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

Tatsächlich gibt es Algorithmen zum Überladen des Rückgabetyps mithilfe von Typinferenz. Es ist eigentlich gar nicht so schwer mit einer Maschine umzugehen, das Problem ist, dass Menschen es schwer finden. (Ich denke, der Umriss ist im Drachenbuch angegeben, der Algorithmus wird Wippalgorithmus genannt, wenn ich mich richtig erinnere.)

Yttrill
quelle
2

Anwendungsfall gegen das Implementieren von Funktionsüberladung: 25 gleichnamige Methoden, die die gleichen Aktionen ausführen, jedoch mit völlig unterschiedlichen Argumenten in einer Vielzahl von Mustern.

Anwendungsfall gegen die Nichtimplementierung von Funktionsüberladungen: 5 Methoden mit ähnlichen Namen und sehr ähnlichen Typengruppen im exakt gleichen Muster.

Letztendlich freue ich mich nicht darauf, die Dokumente für eine API zu lesen, die in beiden Fällen erstellt wurde.

Aber in einem Fall geht es darum, was Benutzer tun könnten. Im anderen Fall müssen die Benutzer dies aufgrund einer Spracheinschränkung tun. IMO, es ist besser, zumindest die Möglichkeit zu berücksichtigen, dass Programmautoren klug genug sind, um vernünftig zu überladen, ohne Mehrdeutigkeiten zu erzeugen. Wenn Sie ihre Hände schlagen und die Option wegnehmen, garantieren Sie im Grunde genommen, dass Unklarheiten auftreten werden. Ich vertraue eher darauf, dass der Benutzer das Richtige tut, als davon auszugehen, dass er immer das Falsche tut. Nach meiner Erfahrung führt Protektionismus zu einem noch schlimmeren Verhalten der Sprachgemeinschaft.

Erik Reppen
quelle
1

Ich habe mich dafür entschieden, in meiner Sprache Felix gewöhnliche Überladungs- und Mehrfachtypklassen anzubieten.

Ich halte (offenes) Überladen für wesentlich, besonders in einer Sprache, die viele numerische Typen hat (Felix hat alle numerischen Typen von C). Im Gegensatz zu C ++, das das Überladen missbraucht, indem Vorlagen davon abhängig gemacht werden, ist der Felix-Polymorphismus jedoch parametrisch: Sie müssen Vorlagen in C ++ überladen, da Vorlagen in C ++ schlecht gestaltet sind.

Typenklassen werden auch in Felix angeboten. Ignorieren Sie diejenigen, die C ++ kennen, aber Haskell nicht befassen, die es als überladen bezeichnen. Es ist nicht wie eine Überladung aus der Ferne, sondern wie eine Template-Spezialisierung: Sie deklarieren ein Template, das Sie nicht implementieren, und stellen dann Implementierungen für bestimmte Fälle zur Verfügung, wenn Sie sie benötigen. Die Typisierung ist parametrisch polymorph, die Implementierung erfolgt durch Ad-hoc-Instanziierung, soll jedoch nicht uneingeschränkt sein: Sie muss die beabsichtigte Semantik implementieren.

In Haskell (und C ++) können Sie die Semantik nicht angeben. In C ++ ist die Idee "Concepts" ungefähr ein Versuch, die Semantik anzunähern. In Felix können Sie die Absicht mit Axiomen, Reduktionen, Lemmas und Theoremen approximieren.

Der Haupt- und einzige Vorteil von (offenem) Überladen in einer Sprache mit guten Prinzipien wie Felix ist, dass es einfacher ist, sich Bibliotheksfunktionsnamen zu merken, sowohl für den Programmierer als auch für den Codeüberprüfer.

Der Hauptnachteil der Überladung ist der komplexe Algorithmus, der zur Implementierung erforderlich ist. Es passt auch nicht sehr gut zu Typinferenz: Obwohl die beiden nicht vollständig exklusiv sind, ist der Algorithmus, um beides zu tun, komplex genug, sodass der Programmierer die Ergebnisse wahrscheinlich nicht vorhersagen kann.

In C ++ ist dies auch ein Problem, da es einen schlampigen Matching-Algorithmus hat und auch automatische Typkonvertierungen unterstützt: In Felix habe ich dieses Problem "behoben", indem eine genaue Übereinstimmung und keine automatischen Typkonvertierungen erforderlich waren.

Ich denke, Sie haben die Wahl: Überladung oder Typinferenz. Inferenz ist nett, aber es ist auch sehr schwer, sie so zu implementieren, dass Konflikte richtig diagnostiziert werden. Ocaml teilt Ihnen beispielsweise mit, wo ein Konflikt erkannt wird, nicht jedoch, woher der erwartete Typ stammt.

Überladen ist nicht viel besser, selbst wenn Sie einen Qualitäts-Compiler haben, der versucht, Ihnen alle Kandidaten mitzuteilen. Es kann schwierig sein, zu erkennen, ob die Kandidaten polymorph sind, und noch schlimmer, wenn es sich um C ++ - Template-Hacker handelt.

Yttrill
quelle
Klingt interessant. Ich würde gerne mehr darüber lesen, aber der Link zu den Dokumenten auf der Felix-Webseite ist kaputt.
Hinweis für sich selbst - denken Sie an einen Namen
Ja, die ganze Seite befindet sich gerade (wieder) im Aufbau. Entschuldigung.
Yttrill
0

Kommt auf den Kontext zurück, aber ich denke, Überladung macht eine Klasse viel nützlicher, wenn ich eine verwende, die von jemand anderem geschrieben wurde. Sie haben häufig weniger Redundanz.

Morgan Herlocker
quelle
0

Wenn Sie Benutzer haben möchten, die mit den Sprachen der C-Familie vertraut sind, sollten Sie dies tun, da Ihre Benutzer dies erwarten.

Conrad Frix
quelle