Pourquoi les langages à typage statique plus courants ne prennent-ils pas en charge la surcharge de fonctions / méthodes par type de retour? Je ne peux penser à rien de tout cela. Cela ne semble pas moins utile ou raisonnable que de prendre en charge la surcharge par type de paramètre. Comment se fait-il qu'il soit tellement moins populaire?
252
Réponses:
Contrairement à ce que disent les autres, la surcharge par type de retour est possible et se fait par certains langages modernes. L'objection habituelle est qu'en code comme
vous ne pouvez pas dire qui
func()
est appelé. Cela peut être résolu de plusieurs manières:int main() { (string)func(); }
.Deux des langues que j'utilise régulièrement ( ab ) utilisent la surcharge par type de retour: Perl et Haskell . Permettez-moi de décrire ce qu'ils font.
En Perl , il existe une distinction fondamentale entre le contexte scalaire et le contexte de liste (et d'autres, mais nous prétendons qu'il y en a deux). Chaque fonction intégrée dans Perl peut faire des choses différentes selon le contexte dans lequel elle est appelée. Par exemple, l'
join
opérateur force le contexte de liste (sur la chose à joindre) tandis que l'scalar
opérateur force le contexte scalaire, alors comparez:Chaque opérateur en Perl fait quelque chose dans un contexte scalaire et quelque chose dans un contexte de liste, et ils peuvent être différents, comme illustré. (Ce n'est pas seulement pour des opérateurs aléatoires comme
localtime
. Si vous utilisez un tableau@a
dans un contexte de liste, il renvoie le tableau, tandis que dans un contexte scalaire, il retourne le nombre d'éléments. Ainsi, par exemple,print @a
imprime les éléments, tout enprint 0+@a
imprime la taille. ) De plus, chaque opérateur peut forcer un contexte, par exemple l'addition+
force le contexte scalaire. Chaque entrée dans lesman perlfunc
documents cela. Par exemple, voici une partie de l'entrée pourglob EXPR
:Maintenant, quelle est la relation entre la liste et le contexte scalaire? Eh bien,
man perlfunc
ditce n'est donc pas une simple question d'avoir une seule fonction, puis vous effectuez une conversion simple à la fin. En fait, j'ai choisi l'
localtime
exemple pour cette raison.Ce ne sont pas seulement les fonctions intégrées qui ont ce comportement. Tout utilisateur peut définir une telle fonction à l'aide de
wantarray
, ce qui vous permet de faire la distinction entre le contexte liste, scalaire et vide. Ainsi, par exemple, vous pouvez décider de ne rien faire si vous êtes appelé dans un contexte nul.Maintenant, vous pouvez vous plaindre que ce n'est pas une surcharge réelle par valeur de retour car vous n'avez qu'une seule fonction, qui est informée du contexte dans lequel elle est appelée, puis agit sur ces informations. Cependant, c'est clairement équivalent (et analogue à la façon dont Perl ne permet pas la surcharge habituelle littéralement, mais une fonction peut simplement examiner ses arguments). De plus, cela résout bien la situation ambiguë mentionnée au début de cette réponse. Perl ne se plaint pas de ne pas savoir quelle méthode appeler; il l'appelle juste. Il suffit de déterminer dans quel contexte la fonction a été appelée, ce qui est toujours possible:
(Remarque: je peux parfois dire opérateur Perl quand je veux dire fonction. Ce n'est pas crucial pour cette discussion.)
Haskell adopte l'autre approche, à savoir de ne pas avoir d'effets secondaires. Il possède également un système de type fort, et vous pouvez donc écrire du code comme suit:
Ce code lit un nombre à virgule flottante à partir de l'entrée standard et imprime sa racine carrée. Mais qu'est-ce qui est surprenant? Eh bien, le type
readLn
estreadLn :: Read a => IO a
. Cela signifie que pour tout type qui peut êtreRead
(formellement, chaque type qui est une instance de laRead
classe de type), ilreadLn
peut le lire. Comment Haskell a-t-il su que je voulais lire un nombre à virgule flottante? Eh bien, le type desqrt
estsqrt :: Floating a => a -> a
, ce qui signifie essentiellement qu'ilsqrt
ne peut accepter que des nombres à virgule flottante comme entrées, et Haskell a donc déduit ce que je voulais.Que se passe-t-il lorsque Haskell ne peut pas déduire ce que je veux? Eh bien, il y a quelques possibilités. Si je n'utilise pas du tout la valeur de retour, Haskell n'appellera tout simplement pas la fonction en premier lieu. Cependant, si je fais utiliser la valeur de retour, alors Haskell se plaindra qu'il ne peut pas déduire le type:
Je peux résoudre l'ambiguïté en spécifiant le type que je veux:
Quoi qu'il en soit, toute cette discussion signifie que la surcharge par valeur de retour est possible et est effectuée, ce qui répond en partie à votre question.
L'autre partie de votre question est pourquoi plus de langues ne le font pas. Je laisserai les autres répondre à cela. Cependant, quelques commentaires: la raison principale est probablement que l'opportunité de confusion est vraiment plus grande ici que dans la surcharge par type d'argument. Vous pouvez également consulter les justifications des différentes langues:
Ada : "Il peut sembler que la règle de résolution de surcharge la plus simple consiste à tout utiliser - toutes les informations d'un contexte aussi large que possible - pour résoudre la référence surchargée. Cette règle peut être simple, mais elle n'est pas utile. Elle nécessite le lecteur humain pour analyser des morceaux de texte arbitrairement grands et pour faire des inférences arbitrairement complexes (comme (g) ci-dessus). Nous pensons qu'une meilleure règle est celle qui rend explicite la tâche qu'un lecteur humain ou un compilateur doit effectuer, et qui rend cette tâche aussi naturel que possible pour le lecteur humain. "
C ++ (sous-section 7.4.1 du «langage de programmation C ++» de Bjarne Stroustrup): «Les types de retour ne sont pas pris en compte dans la résolution de surcharge. La raison est de garder la résolution pour un opérateur individuel ou un appel de fonction indépendant du contexte. Considérez:
Si le type de retour était pris en compte, il ne serait plus possible de regarder un appel de
sqrt()
isolément et de déterminer quelle fonction a été appelée. "(Notez, à titre de comparaison, que dans Haskell, il n'y a pas de conversions implicites .)Java ( Java Language Specification 9.4.1 ): "L'une des méthodes héritées doit être substituable par type de retour pour chaque autre méthode héritée, sinon une erreur de compilation se produit." (Oui, je sais que cela ne donne aucune justification. Je suis sûr que la justification est donnée par Gosling dans "Java Programming Language". Peut-être que quelqu'un en a une copie? Je parie que c'est le "principe de la moindre surprise" en substance. Cependant, fait amusant sur Java: la JVM permet la surcharge par valeur de retour! Ceci est utilisé, par exemple, dans Scala , et est également accessible directement via Java en jouant avec des internes.
PS. Enfin, il est possible de surcharger par valeur de retour en C ++ avec une astuce. Témoin:
la source
Foo
etBar
soutien conversion bidirectionnelle, et une méthode utilise le typeFoo
interne mais le type de retourBar
. Si une telle méthode est appelée par un code qui contraindra immédiatement le résultat à taperFoo
, utiliser leBar
type de retour pourrait fonctionner, mais celui-Foo
ci serait mieux. BTW, j'aimerais aussi voir un moyen par lequel ...var someVar = someMethod();
(ou bien désigner que son retour ne devrait pas être utilisé de cette façon). Par exemple, une famille de types qui implémente une interface Fluent pourrait bénéficier de versions mutables et immuables, doncvar thing2 = thing1.WithX(3).WithY(5).WithZ(9);
aurait uneWithX(3)
copiething1
vers un objet mutable, mutera X et retournerait cet objet mutable;WithY(5)
muterait Y et retournerait le même objet; de même `WithZ (9). Ensuite, l'affectation se convertirait en un type immuable.Si les fonctions étaient surchargées par le type de retour et que vous aviez ces deux surcharges
il n'y a aucun moyen que le compilateur puisse déterminer laquelle de ces deux fonctions appeler en voyant un appel comme celui-ci
Pour cette raison, les concepteurs de langage interdisent souvent la surcharge de valeur de retour.
Certaines langues (comme MSIL), cependant, ne permettent la surcharge par type de retour. Ils sont également confrontés à la difficulté ci-dessus, mais ils ont des solutions de contournement, pour lesquelles vous devrez consulter leur documentation.
la source
Dans une telle langue, comment résoudriez-vous les problèmes suivants:
si
f
avait des surchargesvoid f(int)
etvoid f(string)
etg
avait des surchargesint g(int)
etstring g(int)
? Vous auriez besoin d'une sorte d'ambiguïté.Je pense que les situations où vous pourriez en avoir besoin seraient mieux servies en choisissant un nouveau nom pour la fonction.
la source
Pour voler une réponse spécifique C ++ à une autre question très similaire (dupe?):
Les types de retour de fonction n'entrent pas en jeu dans la résolution de surcharge simplement parce que Stroustrup (je suppose avec l'entrée d'autres architectes C ++) voulait que la résolution de surcharge soit «indépendante du contexte». Voir 7.4.1 - «Surcharge et type de retour» du «Langage de programmation C ++, troisième édition».
Ils voulaient que cela soit basé uniquement sur la façon dont la surcharge était appelée - pas sur la façon dont le résultat était utilisé (s'il l'était). En effet, de nombreuses fonctions sont appelées sans utiliser le résultat ou le résultat serait utilisé dans le cadre d'une expression plus large. Un facteur qui, j'en suis sûr, est entré en jeu lorsqu'ils ont décidé que c'était le cas si le type de retour faisait partie de la résolution, il y aurait de nombreux appels à des fonctions surchargées qui devraient être résolues avec des règles complexes ou nécessiteraient le lancement du compilateur. une erreur que l'appel était ambigu.
Et, Seigneur le sait, la résolution de surcharge C ++ est suffisamment complexe en l'état ...
la source
Dans haskell, c'est possible même s'il n'a pas de surcharge de fonction. Haskell utilise des classes de types. Dans un programme, vous pouvez voir:
La surcharge de fonction elle-même n'est pas si populaire. La plupart des langages que j'ai vus avec sont C ++, peut-être java et / ou C #. Dans toutes les langues dynamiques, c'est un raccourci pour:
Par conséquent, cela ne sert à rien. La plupart des gens ne sont pas intéressés à savoir si la langue peut vous aider à supprimer une seule ligne, peu importe où vous l'utilisez.
La correspondance de modèles est quelque peu similaire à la surcharge de fonctions, et je suppose que cela fonctionne parfois de la même manière. Ce n'est pas courant car il n'est utile que pour quelques programmes et est difficile à implémenter dans la plupart des langues.
Vous voyez, il existe infiniment d'autres fonctionnalités plus faciles à implémenter à implémenter dans le langage, notamment:
la source
Bonnes réponses! La réponse d'A .Rex en particulier est très détaillée et instructive. Comme il le fait remarquer, C ++ prend en compte les opérateurs de conversion de type fournis par l'utilisateur lors de la compilation
lhs = func();
(où func est vraiment le nom d'une structure) . Ma solution de contournement est un peu différente - pas meilleure, juste différente (bien qu'elle soit basée sur la même idée de base).Alors que j'avais voulu écrire ...
Je me suis retrouvé avec une solution qui utilise une structure paramétrée (avec T = le type de retour):
Un avantage de cette solution est que tout code qui inclut ces définitions de modèle peut ajouter plus de spécialisations pour plus de types. Vous pouvez également effectuer des spécialisations partielles de la structure selon vos besoins. Par exemple, si vous vouliez une gestion spéciale pour les types de pointeurs:
En négatif, vous ne pouvez pas écrire
int x = func();
avec ma solution. Vous devez écrireint x = func<int>();
. Vous devez dire explicitement quel est le type de retour, plutôt que de demander au compilateur de le vérifier en regardant les opérateurs de conversion de type. Je dirais que «ma» solution et A.Rex appartiennent toutes les deux à un front pareto-optimal pour résoudre ce dilemme C ++ :)la source
si vous voulez surcharger des méthodes avec différents types de retour, ajoutez simplement un paramètre factice avec une valeur par défaut pour permettre l'exécution de la surcharge, mais n'oubliez pas que le type de paramètre doit être différent afin que la logique de surcharge fonctionne ensuite est par exemple sur delphi:
l'utiliser comme ça
la source
Comme déjà montré - les appels ambigus d'une fonction qui ne diffère que par le type de retour introduit une ambiguïté. L'ambiguïté induit un code défectueux. Un code défectueux doit être évité.
La complexité motivée par la tentative d'ambiguïté montre que ce n'est pas un bon hack. Outre un exercice intellectuel - pourquoi ne pas utiliser des procédures avec des paramètres de référence.
la source
doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
cette fonctionnalité de surcharge n'est pas difficile à gérer, si vous la regardez d'une manière légèrement différente. considérer ce qui suit,
si une langue renvoyait une surcharge, elle autoriserait une surcharge de paramètres, mais pas des duplications. cela résoudrait le problème de:
car il n'y a qu'un seul f (int choix) à choisir.
la source
Dans .NET, nous utilisons parfois un paramètre pour indiquer la sortie souhaitée à partir d'un résultat générique, puis nous effectuons une conversion pour obtenir ce que nous attendons.
C #
Peut-être que cet exemple pourrait aussi aider:
C ++
la source
Pour mémoire, Octave permet un résultat différent selon que l'élément de retour est scalaire vs tableau.
Cf également Décomposition en valeurs singulières .
la source
Celui-ci est légèrement différent pour C ++; Je ne sais pas si cela serait considéré comme une surcharge par type de retour directement. Il s'agit plutôt d'une spécialisation de modèle qui agit à la manière de.
util.h
util.inl
util.cpp
Cet exemple n'utilise pas exactement la résolution de surcharge de fonction par type de retour, mais cette classe non objet c ++ utilise la spécialisation de modèle pour simuler la résolution de surcharge de fonction par type de retour avec une méthode statique privée.
Chacune des
convertToType
fonctions appelle le modèle de fonctionstringToValue()
et si vous regardez les détails d'implémentation ou l'algorithme de ce modèle de fonction, il appellegetValue<T>( param, param )
et retourne un typeT
et le stocke dans unT*
qui est passé dans lestringToValue()
modèle de fonction comme l'un de ses paramètres. .Autre que quelque chose comme ça; C ++ n'a pas vraiment de mécanisme pour avoir une résolution de surcharge de fonction par type de retour. Il peut y avoir d'autres constructions ou mécanismes que je ne connais pas qui pourraient simuler la résolution par type de retour.
la source
Je pense que c'est un GAP dans la définition C ++ moderne… pourquoi?
Pourquoi un compilateur C ++ ne peut-il pas générer d'erreur dans l'exemple "3" et accepter le code dans l'exemple "1 + 2" ??
la source
La plupart des langages statiques prennent également en charge les génériques, ce qui résoudrait votre problème. Comme indiqué précédemment, sans avoir de différences de paramètres, il n'y a aucun moyen de savoir lequel appeler. Donc, si vous voulez le faire, utilisez simplement des génériques et appelez-le un jour.
la source