Surcharge de fonction? Oui ou non [fermé]


16

Je suis en train de développer un langage compilé de manière statique et fortement typée, et je revisite l'idée d'inclure ou non la surcharge de fonctions comme fonctionnalité de langage. J'ai réalisé que je suis un peu biaisé, venant principalement d'un C[++|#]arrière - plan.

Quels sont les arguments les plus convaincants pour et contre l' inclusion de la surcharge de fonctions dans un langage?


EDIT: Y a - t-il personne qui a une opinion opposée?

Bertrand Meyer (créateur d'Eiffel en 1985/1986) appelle une méthode surchargeant ceci: (source)

un mécanisme de vanité qui n'apporte rien à la puissance sémantique d'un langage OO, mais entrave la lisibilité et complique la tâche de chacun

Maintenant, ce sont des généralisations radicales, mais c'est un gars intelligent, donc je pense qu'il est sûr de dire qu'il pourrait les sauvegarder s'il en avait besoin. En fait, il avait presque convaincu Brad Abrams (l'un des développeurs CLSv1) que .NET ne devait pas prendre en charge la surcharge de méthode. (source) C'est quelque chose de puissant. Quelqu'un peut-il éclairer ses pensées et savoir si son point de vue est toujours justifié 25 ans plus tard?

Réponses:


24

La surcharge de fonctions est absolument critique pour le code de modèle de style C ++. Si je dois utiliser des noms de fonction différents pour différents types, je ne peux pas écrire de code générique. Cela éliminerait une partie importante et très utilisée de la bibliothèque C ++ et une grande partie des fonctionnalités de C ++.

Il est généralement présent dans les noms des fonctions membres. A.foo()peut appeler une fonction entièrement différente de B.foo(), mais les deux fonctions sont nommées foo. Il est présent dans les opérateurs, tout comme +différentes choses lorsqu'il est appliqué à des nombres entiers et à virgule flottante, et il est souvent utilisé comme opérateur de concaténation de chaînes. Il semble étrange de ne pas le permettre également dans les fonctions régulières.

Il permet l'utilisation de "multiméthodes" de style Common Lisp, dans lesquelles la fonction exacte appelée dépend de deux types de données. Si vous n'avez pas programmé dans le système d'objets Common Lisp, essayez-le avant d'appeler cela inutile. C'est vital pour les flux C ++.

Les E / S sans surcharge de fonctions (ou fonctions variadiques, qui sont pires) nécessiteraient un certain nombre de fonctions différentes, soit pour imprimer des valeurs de différents types, soit pour convertir des valeurs de différents types en un type commun (comme String).

Sans surcharge de fonction, si je change le type d'une variable ou d'une valeur, je dois changer chaque fonction qui l'utilise. Il est beaucoup plus difficile de refactoriser le code.

Il facilite l'utilisation des API lorsque l'utilisateur n'a pas à se rappeler quelle convention de dénomination de type est utilisée, et l'utilisateur peut simplement se souvenir des noms de fonction standard.

Sans surcharge d'opérateur, il faudrait étiqueter chaque fonction avec les types qu'elle utilise, si cette opération de base peut être utilisée sur plusieurs types. Il s'agit essentiellement de la notation hongroise, la mauvaise façon de le faire.

Dans l'ensemble, cela rend un langage beaucoup plus utilisable.


1
+1, tous de très bons points. Et croyez-moi, je ne pense pas que les multiméthodes soient inutiles ... Je maudis le clavier même sur lequel je tape chaque fois que je suis obligé d'utiliser le modèle de visiteur.
Note à soi-même - pensez à un nom

Ce type ne décrit-il pas la fonction comme étant prioritaire et non surchargée?
dragosb

8

Je recommande au moins de connaître les classes de types dans Haskell. Les classes de types ont été créées pour être une approche disciplinée de la surcharge des opérateurs, mais ont trouvé d'autres utilisations et, dans une certaine mesure, ont fait de Haskell ce qu'il est.

Par exemple, voici un exemple de surcharge ad-hoc (pas tout à fait valide Haskell):

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

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

Et voici le même exemple de surcharge avec des classes de type:

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

Un inconvénient est que vous devez trouver des noms funky pour toutes vos classes de types (comme dans Haskell, vous avez la plutôt abstraite Monad, Functor, Applicativeainsi que le plus simple et plus reconnaissable Eq, Numet Ord).

Un avantage est que, une fois que vous êtes familiarisé avec une classe de types, vous savez comment utiliser n'importe quel type de cette classe. De plus, il est facile de protéger les fonctions des types qui n'implémentent pas les classes nécessaires, comme dans:

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

Edit: Dans Haskell, si vous vouliez un ==opérateur qui accepte deux types différents, vous pouvez utiliser une classe de type multi-paramètres:

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

Bien sûr, c'est probablement une mauvaise idée, car cela vous permet explicitement de comparer des pommes et des oranges. Cependant, vous voudrez peut-être considérer cela +, car ajouter un Word8à Intest vraiment une chose sensée à faire dans certains contextes.


+1, j'essaie de comprendre ce concept depuis que je l'ai lu pour la première fois, et cela aide. Ce paradigme rend-il impossible de définir, par exemple, (==) :: Int -> Float -> Booln'importe où? (bien que ce soit une bonne idée, bien sûr)
Note à soi-même - pensez à un nom

Si vous autorisez les classes de type multi-paramètres (que Haskell prend en charge en tant qu'extension), vous le pouvez. J'ai mis à jour la réponse avec un exemple.
Joey Adams

Hum ... intéressant. Donc, fondamentalement, class Eq a ...traduit en pseudo-famille C serait interface Eq<A> {bool operator==(A x, A y);}, et au lieu d'utiliser du code basé sur des modèles pour comparer des objets arbitraires, vous utilisez cette «interface». Est-ce correct?
Note à soi-même - pensez à un nom

Droite. Vous voudrez peut-être également jeter un coup d'œil aux interfaces dans Go. À savoir, vous n'avez pas à déclarer un type comme implémentant une interface, il vous suffit d'implémenter toutes les méthodes pour cette interface.
Joey Adams

1
@ Notetoself-thinkofaname: Concernant les opérateurs supplémentaires - oui et non. Il permet ==d'être dans un espace de noms différent mais ne permet pas de le remplacer. Veuillez noter qu'un seul espace de noms ( Prelude) est inclus par défaut mais vous pouvez empêcher le chargement en utilisant des extensions ou en l'important explicitement ( import Prelude ()n'importera rien Preludeet import qualified Prelude as Pn'insérera pas de symboles dans l'espace de noms actuel).
Maciej Piechotka

4

Autoriser la surcharge de fonctions, vous ne pouvez pas faire ce qui suit avec des paramètres facultatifs (ou si vous le pouvez, pas bien).

exemple trivial suppose aucune base.ToString() méthode

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

Pour une langue fortement typée, oui. Pour une langue faiblement typée, non. Vous pouvez faire ce qui précède avec une seule fonction dans une langue faiblement typée.
spex

2

J'ai toujours préféré les paramètres par défaut à la surcharge de fonctions. Les fonctions surchargées appellent généralement une version "par défaut" avec des paramètres par défaut. Pourquoi écrire

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

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

Quand je pourrais faire:

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

Cela dit, je réalise que les fonctions surchargées font des choses différentes, plutôt que d'appeler simplement une autre variante avec des paramètres par défaut ... mais dans un tel cas, ce n'est pas une mauvaise idée (en fait, c'est probablement un bonne idée) de lui donner juste un nom différent.

(De plus, les arguments de mots clés de style Python fonctionnent très bien avec les paramètres par défaut.)


D' accord, nous allons essayer à nouveau que, et je vais essayer de comprendre cette fois ... Qu'en est- il Array Slice(int start, int length) {...}surchargé Array Slice(int start) {return this.Slice(start, this.Count - start);}? Cela ne peut pas être codé à l'aide de paramètres par défaut. Pensez-vous qu'il faudrait leur donner des noms différents? Si oui, comment les nommeriez-vous?
Note à soi-même - pensez à un nom du

Cela ne concerne pas toutes les utilisations de la surcharge.
MetalMikester

@MetalMikester: Quelles utilisations pensez-vous que j'ai raté?
mipadi

@mipadi Il manque des choses comme indexOf(char ch)+ indexOf(Date dt)sur une liste. J'aime aussi les valeurs par défaut, mais elles ne sont pas interchangeables avec la frappe statique.
Mark

2

Vous venez de décrire Java. Ou C #.

Pourquoi réinventes-tu la roue?

Assurez-vous que le type de retour fait partie de la signature de la méthode et du contenu Overload to your hearts, il nettoie vraiment le code lorsque vous n'avez pas à le dire.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }

7
Il y a une raison pour laquelle le type de retour ne fait pas partie de la signature de la méthode - ou du moins pas une partie qui peut être utilisée pour la résolution de surcharge - dans n'importe quelle langue que je connaisse. Lorsque vous appelez une fonction en tant que procédure, sans affecter le résultat à une variable ou à une propriété, comment le compilateur est-il censé déterminer la version à invoquer si tous les autres arguments sont identiques?
Mason Wheeler

@Mason: Vous pourriez détecter heuristiquement le type de retour attendu en fonction de ce qui devrait être renvoyé, mais je ne m'attends pas à ce que cela soit fait.
Josh K

1
Umm ... comment votre heuristique sait-elle ce qui devrait être retourné lorsque vous n'attendez aucune valeur de retour?
Mason Wheeler

1
L'heuristique d'attente de type est en place ... vous pouvez faire des choses comme EnumX.Flag1 | Flag2 | Flag3. Je ne mettrai cependant pas cela en œuvre. Si je l'ai fait et que le type de retour n'a pas été utilisé, je rechercherais un type de retour de void.
Note à soi-même - pensez à un nom le

1
@Mason: C'est une bonne question, mais dans ce cas, je chercherais une fonction void (comme mentionné). En théorie, vous pouvez également choisir l'un d'eux, car ils remplissent tous la même fonction, il suffit de renvoyer les données dans un format différent.
Josh K

2

Grrr .. pas encore assez de privilège pour commenter ..

@Mason Wheeler: Faites attention alors à Ada, qui surcharge le type de retour. De plus, mon langage Felix le fait aussi dans certains contextes, en particulier lorsqu'une fonction renvoie une autre fonction et qu'il y a un appel comme:

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

le type de b peut être utilisé pour la résolution de surcharge. Surcharge également C ++ sur le type de retour dans certaines circonstances:

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

En fait, il existe des algorithmes de surcharge sur le type de retour, utilisant l'inférence de type. Ce n'est pas si difficile à faire avec une machine, le problème est que les humains ont du mal. (Je pense que le plan est donné dans le Dragon Book, l'algorithme est appelé l'algorithme de bascule si je me souviens bien)


2

Cas d'utilisation contre l'implémentation de la surcharge de fonctions: 25 méthodes portant le même nom qui font en quelque sorte les mêmes choses mais avec des ensembles d'arguments complètement différents dans une grande variété de modèles.

Cas d'utilisation contre l'implémentation de la surcharge de fonctions: 5 méthodes de même nom avec des ensembles de types très similaires dans le même modèle exact.

À la fin de la journée, je ne suis pas impatient de lire les documents pour une API produite dans les deux cas.

Mais dans un cas, il s'agit de ce que les utilisateurs pourraient faire. Dans l'autre cas, c'est ce que les utilisateurs doivent faire en raison d'une restriction de langue. OMI, il est préférable de permettre au moins la possibilité aux auteurs de programmes d'être suffisamment intelligents pour surcharger sensiblement sans créer d'ambiguïté. Lorsque vous vous giflez les mains et retirez l'option, vous garantissez essentiellement que l'ambiguïté va se produire. Je préfère faire confiance à l'utilisateur pour faire la bonne chose que de supposer qu'il fera toujours la mauvaise chose. D'après mon expérience, le protectionnisme a tendance à conduire à un comportement encore pire de la part d'une communauté linguistique.


1

J'ai choisi de fournir une surcharge ordinaire et des classes de types multi-types dans mon langage Felix.

Je considère que la surcharge (ouverte) est essentielle, en particulier dans un langage qui a beaucoup de types numériques (Felix a tous les types numériques de C). Cependant, contrairement à C ++ qui abuse de la surcharge en faisant dépendre les modèles, le polymorphisme Felix est paramétrique: vous avez besoin d'une surcharge pour les modèles en C ++ car les modèles en C ++ sont mal conçus.

Les classes de type sont également fournies dans Felix. Pour ceux qui connaissent le C ++ mais qui ne plaisantent pas avec Haskell, ignorez ceux qui le décrivent comme une surcharge. Ce n'est pas comme une surcharge à distance, c'est plutôt comme une spécialisation de modèle: vous déclarez un modèle que vous n'implémentez pas, puis fournissez des implémentations pour des cas particuliers selon vos besoins. Le typage est paramétriquement polymorphe, l'implémentation se fait par instanciation ad hoc mais elle n'est pas destinée à être sans contrainte: elle doit implémenter la sémantique voulue.

Dans Haskell (et C ++), vous ne pouvez pas énoncer la sémantique. En C ++, l'idée "Concepts" est à peu près une tentative d'approximation de la sémantique. Dans Felix, vous pouvez approximer l'intention avec des axiomes, des réductions, des lemmes et des théorèmes.

Le principal, et seulement avantage de la surcharge (ouverte) dans un langage bien fondé sur des principes comme Felix est qu'il facilite la mémorisation des noms de fonction de bibliothèque, à la fois pour l'auteur du programme et pour le réviseur de code.

Le principal inconvénient de la surcharge est l'algorithme complexe requis pour l'implémenter. Cela ne correspond pas non plus très bien à l'inférence de type: bien que les deux ne soient pas entièrement exclusifs, l'algorithme pour faire les deux est suffisamment complexe, le programmeur ne serait probablement pas en mesure de prédire les résultats.

En C ++, c'est aussi un problème car il a un algorithme de correspondance bâclé et prend également en charge les conversions de type automatiques: dans Felix, j'ai "corrigé" ce problème en exigeant une correspondance exacte et aucune conversion de type automatique.

Vous avez donc un choix, je pense: surcharge ou inférence de type. L'inférence est mignonne, mais elle est également très difficile à mettre en œuvre d'une manière qui diagnostique correctement les conflits. Ocaml, par exemple, vous indique d'où il détecte un conflit, mais pas d'où il a déduit le type attendu.

La surcharge n'est pas beaucoup mieux, même si vous avez un compilateur de qualité qui essaie de vous dire tous les candidats, il peut être difficile de lire si les candidats sont polymorphes, et pire encore s'il s'agit de piratage de modèles C ++.


Ça semble intéressant. J'adorerais en lire plus, mais le lien vers les documents sur la page Web de Felix est rompu.
Note à soi-même - pensez à un nom

Oui, l'ensemble du site est actuellement en construction (encore), désolé.
Yttrill

0

Cela revient au contexte, mais je pense que la surcharge rend une classe beaucoup plus utile lorsque j'en utilise une écrite par quelqu'un d'autre. Vous vous retrouvez souvent avec moins de redondance.


0

Si vous cherchez à avoir des utilisateurs familiarisés avec les langages de la famille C, alors oui, vous devriez le faire, car vos utilisateurs s'y attendront.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.