Est-ce une mauvaise idée de renvoyer différents types de données à partir d'une seule fonction dans un langage typé dynamiquement?

65

Ma langue principale est typée de manière statique (Java). En Java, vous devez renvoyer un type unique de chaque méthode. Par exemple, vous ne pouvez pas avoir une méthode qui retourne conditionnellement un Stringou retourne conditionnellement un Integer. Mais en JavaScript, par exemple, c'est très possible.

Dans un langage typé statiquement, je comprends pourquoi c'est une mauvaise idée. Si toutes les méthodes sont renvoyées Object(le parent commun dont toutes les classes héritent), vous et le compilateur n'avez aucune idée de ce à quoi vous faites face. Vous devrez découvrir toutes vos erreurs au moment de l'exécution.

Mais dans un langage typé dynamiquement, il peut ne pas y avoir de compilateur. Dans un langage typé dynamiquement, il n’est pas évident pour moi qu’une fonction renvoyant plusieurs types soit une mauvaise idée. Mon expérience dans les langages statiques me permet d'éviter d'écrire de telles fonctions, mais je crains de ne pas être attentive à une fonctionnalité qui pourrait rendre mon code plus propre d'une manière que je ne peux pas voir.


Edit : Je vais supprimer mon exemple (jusqu'à ce que je puisse penser à un meilleur). Je pense que cela incite les gens à répondre à un point que je n'essaie pas de dire.

Daniel Kaplan
la source
Pourquoi ne pas lancer une exception dans le cas d'erreur?
TrueWill
1
@ TrueWill, je réponds à cela dans la phrase suivante.
Daniel Kaplan
Avez-vous remarqué qu'il s'agit d'une pratique courante dans des langages plus dynamiques? C'est courant dans les langages fonctionnels , mais là les règles sont très explicites. Et je sais qu’il est courant, en particulier en JavaScript, d’autoriser les arguments de types différents (c’est essentiellement le seul moyen de surcharger une fonction), mais j’ai rarement vu cela s’appliquer aux valeurs renvoyées. Le seul exemple auquel je peux penser est PowerShell avec son encapsulation / décompactage de tableaux essentiellement automatique, et les langages de script constituent un cas exceptionnel.
Aaronaught
3
Non mentionné, mais il existe de nombreux exemples (même dans Java Generics) de fonctions qui prennent un type de retour comme paramètre; Par exemple, dans Common Lisp, nous avons des (coerce var 'string)rendements a stringou (concatenate 'string this that the-other-thing)similaires. J'ai écrit des choses comme ThingLoader.getThingById (Class<extends FindableThing> klass, long id)aussi bien. Et, là, je pourrais seulement renvoyer quelque chose qui sous-classe ce que vous avez demandé: loader.getThingById (SubclassA.class, 14)peut renvoyer un SubclassBqui s'étend SubclassA...
BRPocock le
1
Les langues typées dynamiquement sont comme la Spoon dans le film The Matrix . N'essayez pas de définir la cuillère comme une chaîne ou un nombre . Ce serait impossible. Au lieu de cela ... seulement essayer de comprendre la vérité. Qu'il n'y a pas de cuillère .
Reactgular

Réponses:

42

Contrairement à d’autres réponses, il existe des cas où le retour de différents types est acceptable.

Exemple 1

sum(2, 3)  int
sum(2.1, 3.7)  float

Dans certains langages à typage statique, cela implique des surcharges. Nous pouvons donc considérer qu'il existe plusieurs méthodes, chacune renvoyant le type prédéfini et fixe. Dans les langages dynamiques, il peut s'agir de la même fonction, implémentée sous la forme:

var sum = function (a, b) {
    return a + b;
};

Même fonction, différents types de valeur de retour.

Exemple 2

Imaginez que vous obteniez une réponse d'un composant OpenID / OAuth. Certains fournisseurs OpenID / OAuth peuvent contenir plus d'informations, telles que l'âge de la personne.

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Facebook',
//     name: {
//         firstName: 'Hello',
//         secondName: 'World',
//     },
//     email: '[email protected]',
//     age: 27
// }

D'autres auraient le minimum, s'agirait-il d'une adresse e-mail ou d'un pseudonyme?

var user = authProvider.findCurrent();
// user is now:
// {
//     provider: 'Google',
//     email: '[email protected]'
// }

Encore une fois, même fonction, résultats différents.

Ici, l'avantage de renvoyer différents types est particulièrement important dans un contexte où vous ne vous souciez pas des types et des interfaces, mais des objets réellement contenus. Par exemple, imaginons qu'un site Web contienne un langage mature. Ensuite, le findCurrent()peut être utilisé comme ceci:

var user = authProvider.findCurrent();
if (user.age || 0 >= 16) {
    // The person can stand mature language.
    allowShowingContent();
} else if (user.age) {
    // OpenID/OAuth gave the age, but the person appears too young to see the content.
    showParentalAdvisoryRequestedMessage();
} else {
    // OpenID/OAuth won't tell the age of the person. Ask the user himself.
    askForAge();
}

Refactoriser cela en code où chaque fournisseur aura sa propre fonction qui retournera un type fixe bien défini dégraderait non seulement la base de code et entraînerait la duplication de code, mais n'apporterait aucun avantage. On peut finir par faire des horreurs comme:

var age;
if (['Facebook', 'Yahoo', 'Blogger', 'LiveJournal'].contains(user.provider)) {
    age = user.age;
}
Arseni Mourzenko
la source
5
"Dans les langages à typage statique, cela implique des surcharges" Je pense que vous vouliez dire "dans certains langages à typage statique" :) Les bons langages à typage statique ne nécessitent pas de surcharge pour quelque chose comme votre sumexemple.
Andres F.
17
Pour votre exemple précis, pensez à Haskell: sum :: Num a => [a] -> a. Vous pouvez résumer une liste de tout ce qui est un nombre. Contrairement à javascript, si vous essayez de résumer quelque chose qui n'est pas un nombre, l'erreur sera interceptée lors de la compilation.
Andres F.
3
@MainMa En Scala, Iterator[A]possède une méthode def sum[B >: A](implicit num: Numeric[B]): Bqui permet à nouveau de faire la somme de tout type de nombre et qui est vérifiée au moment de la compilation.
Petr Pudlák le
3
@Bakuriu Bien sûr, vous pouvez écrire une telle fonction, bien que vous deviez au préalable surcharger les +chaînes (en implémentant Num, ce qui est une idée terrible en termes de conception mais légale) ou inventer un autre opérateur / fonction surchargé par des entiers et des chaînes. respectivement. Haskell a un polymorphisme ad hoc via des classes de types. Une liste contenant à la fois des entiers et des chaînes est beaucoup plus dure (éventuellement impossible sans extensions de langage) mais assez différente.
2
Je ne suis pas particulièrement impressionné par votre deuxième exemple. Ces deux objets sont du même "type" conceptuel, mais dans certains cas, certains champs ne sont pas définis ou renseignés. Vous pourriez très bien représenter cela, même dans un langage avec des nullvaleurs statiques .
Aaronaught
31

En général, c’est une mauvaise idée pour les mêmes raisons que l’équivalent moral dans un langage à typage statique est une mauvaise idée: vous ne savez pas quel type concret est renvoyé, vous n’avez donc aucune idée de ce que vous pouvez faire du résultat (à part le peu de choses qui peuvent être faites avec n'importe quelle valeur). Dans un système de types statiques, vous avez des annotations de types de retour vérifiées par le compilateur, mais dans un langage dynamique, les mêmes connaissances existent toujours: elles sont simplement informelles et stockées dans le cerveau et la documentation plutôt que dans le code source.

Dans de nombreux cas, cependant, il existe une rime et une raison pour lesquelles le type est renvoyé et l’effet est similaire à la surcharge ou au polymorphisme paramétrique dans les systèmes de type statique. En d'autres termes, le type de résultat est prévisible, ce n'est tout simplement pas aussi simple à exprimer.

Mais notez qu’il peut y avoir d’autres raisons pour lesquelles une fonction spécifique est mal conçue: Par exemple, une sumfonction renvoyant la valeur false sur des entrées non valides est une mauvaise idée, car cette valeur est inutile et source d’erreurs (0 <-> fausse confusion).


la source
40
Mes deux cents: Je déteste quand ça se produit. J'ai vu des bibliothèques JS renvoyant la valeur null lorsqu'il n'y a aucun résultat, un objet sur un résultat et un tableau sur deux résultats ou plus. Ainsi, au lieu de simplement parcourir un tableau, vous devez déterminer s'il est nul, et si ce n'est pas le cas, si c'est un tableau et si c'est le cas, faites une chose, sinon faites autre chose. D'autant que, dans le sens habituel du terme, tout développeur sain d'esprit ne fera qu'ajouter l'objet à un tableau et recycler la logique du programme à partir de la manipulation d'un tableau.
Phyrfox
10
@Izkata: Je ne pense pas qu'il s'agisse d'un NaN"contrepoint". NaN est en fait une entrée valide pour tout calcul en virgule flottante. Vous pouvez faire n'importe quoi avec ce NaNque vous pourriez faire avec un nombre réel. Certes, le résultat final dudit calcul ne vous sera peut-être pas très utile, mais il ne provoquera pas d'étranges erreurs d'exécution et vous n'avez pas besoin de le vérifier tout le temps - vous pouvez simplement vérifier une fois à la fin d'un processus. série de calculs. NaN n'est pas un type différent , c'est simplement une valeur spéciale , un peu comme un objet null .
Aaronaught
5
@Aaronaught D'un autre côté, NaNcomme l'objet null, tend à se cacher lorsque des erreurs se produisent, uniquement pour qu'elles apparaissent plus tard. Par exemple, votre programme risque d’exploser s’il se NaNglisse dans une boucle conditionnelle.
Izkata
2
@Izkata: NaN et null ont des comportements totalement différents. Une référence nulle explose dès l'accès, un NaN se propage. La raison pour laquelle cette dernière se produit est évidente, cela vous permet d’écrire des expressions mathématiques sans avoir à vérifier le résultat de chaque sous-expression. Personnellement, je considère que null est beaucoup plus dommageable, car NaN représente un concept numérique approprié sans lequel vous ne pouvez pas vous échapper et, mathématiquement, propager est le comportement correct.
Phoshi
4
Je ne sais pas pourquoi cela s'est transformé en un argument "nul contre tout le reste". Je faisais simplement remarquer que la NaNvaleur est (a) n'est pas réellement un type de retour différent et (b) a une sémantique en virgule flottante bien définie et que, par conséquent, ne peut pas être considéré comme un analogue d'une valeur de retour de type variable. Ce n'est pas si différent de zéro en arithmétique entière, vraiment; Si un zéro se glisse "accidentellement" dans vos calculs, vous risquez souvent de vous retrouver avec un résultat nul ou une erreur de division par zéro. Les valeurs "infini" définies par la virgule flottante IEEE sont-elles également mauvaises?
Aaronaught
26

Dans les langages dynamiques, vous ne devriez pas demander si vous renvoyez des types différents mais des objets avec différentes API . Comme la plupart des langages dynamiques ne s’intéressent pas vraiment aux types, utilisez différentes versions de la saisie de code .

Lors du retour de différents types ont un sens

Par exemple, cette méthode a du sens:

def load_file(file): 
    if something: 
       return ['a ', 'list', 'of', 'strings'] 
    return open(file, 'r')

Parce que le fichier et une liste de chaînes sont (en Python) des itérables qui renvoient une chaîne. Types très différents, même API (à moins que quelqu'un essaie d'appeler des méthodes de fichier sur une liste, mais l'histoire est différente).

Vous pouvez retourner conditionnellement listou tuple( tupleest une liste immuable en python).

Formellement même en train de faire:

def do_something():
    if ...: 
        return None
    return something_else

ou:

function do_something(){
   if (...) return null; 
   return sth;
}

renvoie différents types, car python Noneet Javascript nullsont des types à part entière.

Tous ces cas d'utilisation auraient leur contrepartie dans des langages statiques, la fonction ne ferait que renvoyer une interface appropriée.

Quand renvoyer des objets avec une API différente de manière conditionnelle est une bonne idée

Quant à savoir si le retour de différentes API est une bonne idée, dans la plupart des cas, cela n’a aucun sens. @MainMa a déclaré que seul un exemple judicieux qui me vient à l’esprit est le suivant : lorsque votre API peut fournir des quantités de détails variables, il peut être judicieux de renvoyer plus de détails, le cas échéant.

jb.
la source
4
Bonne réponse. Il convient de noter que l’équivalent typé Java / statique consiste à faire en sorte qu’une fonction renvoie une interface plutôt qu’un type concret spécifique, l’interface représentant l’API / abstraction.
Mikera
1
Je pense que vous êtes en train de chercher, en Python, une liste et un tuple peuvent être de "types" différents, mais ils sont du même "type de canard", c'est-à-dire qu'ils supportent le même ensemble d'opérations. Et des cas comme celui-ci expliquent exactement pourquoi le typage de canard a été introduit. Vous pouvez aussi faire un long x = getInt () en Java.
Kaerber
«Renvoyer différents types» signifie «renvoyer des objets avec différentes API». (Certaines langues font de la manière dont l'objet est construit une partie fondamentale de l'API, d'autres non; c'est une question d'expression du système de types.) Dans votre exemple liste / fichier, do_fileretourne un itérable de chaînes dans les deux sens.
Gilles 'SO- arrête d'être méchant'
1
Dans cet exemple Python, vous pouvez également appeler iter()la liste et le fichier pour vous assurer que le résultat ne peut être utilisé comme itérateur que dans les deux cas.
RemcoGerlich
1
Le retour None or somethingest une excellente idée pour l'optimisation des performances réalisée par des outils tels que PyPy, Numba, Pyston et autres. C’est l’un des pythons qui rend python pas si vite.
Matt
9

Votre question me donne envie de pleurer un peu. Pas pour l'exemple d'utilisation que vous avez fourni, mais parce que quelqu'un va involontairement aller trop loin dans cette approche. C'est un pas en avant d'un code ridiculement incontrôlable.

Le cas d'utilisation de la condition d'erreur est logique, et le modèle nul (tout doit être un modèle) dans les langages à typage statique fait le même type de chose. Votre appel de fonction retourne un objectou il retourne null.

Mais il n'y a qu'un pas à dire : « Je vais l' utiliser pour créer un modèle d'usine » et le retour soit fooou barou bazselon l'humeur de la fonction. Déboguer ceci deviendra un cauchemar quand l'appelant s'attend à ce qu'il foosoit donné bar.

Donc, je ne pense pas que vous soyez fermé d'esprit. Vous faites preuve de la plus grande prudence lorsque vous utilisez les fonctionnalités de la langue.

Divulgation: mon expérience est dans les langages à typage statique et j'ai généralement travaillé pour des équipes plus grandes et variées où le besoin de code maintenable était assez élevé. Donc, ma perspective est probablement biaisée aussi.

doppelgreener
la source
6

L'utilisation de génériques en Java vous permet de renvoyer un type différent tout en conservant la sécurité du type statique. Vous spécifiez simplement le type que vous voulez renvoyer dans le paramètre de type générique de l'appel de fonction.

La question de savoir si vous pouvez utiliser une approche similaire en Javascript est bien sûr une question ouverte. Puisque le Javascript est un langage typé dynamiquement, retourner un objectsemble être le choix évident.

Si vous souhaitez savoir où un scénario de retour dynamique peut fonctionner lorsque vous avez l'habitude de travailler dans un langage à typage statique, envisagez de rechercher le dynamicmot clé en C #. Rob Conery a réussi à écrire un mappeur relationnel-objet dans 400 lignes de code à l'aide du dynamicmot - clé.

Bien entendu, la dynamicseule chose à faire est d’envelopper une objectvariable avec une sécurité de type exécution.

Robert Harvey
la source
4

Je pense que c'est une mauvaise idée de retourner différents types sous condition. Pour moi, cela se produit fréquemment si la fonction peut renvoyer une ou plusieurs valeurs. Si une seule valeur doit être renvoyée, il peut sembler raisonnable de la renvoyer plutôt que de la placer dans un tableau afin d'éviter d'avoir à la décompresser dans la fonction appelante. Toutefois, cela (et la plupart des autres exemples) oblige l'appelant à distinguer les deux types et à les gérer. La fonction sera plus facile à raisonner si elle retourne toujours le même type.

utilisateur116240
la source
4

La "mauvaise pratique" existe, que votre langue soit typée de manière statique ou non. Le langage statique fait plus pour vous éloigner de ces pratiques, et vous pourriez trouver plus d'utilisateurs qui se plaignent de "mauvaise pratique" dans un langage statique parce que c'est un langage plus formel. Cependant, les problèmes sous-jacents existent dans un langage dynamique et vous pouvez déterminer s'ils sont justifiés.

Voici la partie contestable de ce que vous proposez. Si je ne sais pas quel type est renvoyé, je ne peux pas utiliser la valeur de retour immédiatement. Je dois "découvrir" quelque chose à ce sujet.

total = sum_of_array([20, 30, 'q', 50])
if (type_of(total) == Boolean) {
  display_error(...)
} else {
  record_number(total)
}

Souvent, ce type de changement de code est simplement une mauvaise pratique. Cela rend le code plus difficile à lire. Dans cet exemple, vous voyez pourquoi le lancement et la capture de cas d'exception sont populaires. En d'autres termes: si votre fonction ne peut pas faire ce qu'elle dit, elle ne devrait pas revenir avec succès . Si j'appelle votre fonction, je veux faire ceci:

total = sum_of_array([20, 30, 'q', 50])
display_number(total)

Comme la première ligne est renvoyée avec succès, je suppose que celle-ci totalcontient en réalité la somme du tableau. S'il ne revient pas correctement, nous passons à une autre page de mon programme.

Prenons un autre exemple qui ne concerne pas uniquement la propagation d’erreurs. Peut-être que sum_of_array essaie d'être intelligent et de retourner une chaîne lisible par l'homme dans certains cas, comme "C'est ma combinaison de casier!" si et seulement si le tableau est [11,7,19]. J'ai du mal à penser à un bon exemple. Quoi qu'il en soit, le même problème s'applique. Vous devez inspecter la valeur de retour avant de pouvoir faire quoi que ce soit avec elle:

total = sum_of_array([20, 30, 40, 50])
if (type_of(total) == String) {
  write_message(total)
} else {
  record_number(total)
}

Vous pourriez faire valoir qu'il serait utile que la fonction renvoie un entier ou un float, par exemple:

sum_of_array(20, 30, 40) -> int
sum_of_array(23.45, 45.67, 67.789044) -> float

Mais ces résultats ne sont pas différents, en ce qui vous concerne. Vous allez les traiter tous les deux comme des chiffres, et c'est tout ce qui compte pour vous. Donc, sum_of_array retourne le type de nombre. C'est ce que le polymorphisme est à propos.

Il y a donc des pratiques que vous pourriez enfreindre si votre fonction peut renvoyer plusieurs types. Les connaître vous aidera à déterminer si votre fonction particulière doit toujours renvoyer plusieurs types.

Travis Wilson
la source
Désolé, je vous ai égaré avec mon pauvre exemple. Oubliez je l'ai mentionné en premier lieu. Votre premier paragraphe est sur le point. J'aime aussi votre deuxième exemple.
Daniel Kaplan
4

En réalité, il n’est pas rare de renvoyer différents types, même dans un langage à typage statique. C'est pourquoi nous avons des types d'union, par exemple.

En fait, les méthodes en Java renvoient presque toujours l’un des quatre types: un type d’objet nullou une exception, ou elles ne le sont jamais.

Dans de nombreuses langues, les conditions d'erreur sont modélisées comme des sous-routines renvoyant un type de résultat ou un type d'erreur. Par exemple, en Scala:

def transferMoney(amount: Decimal): Either[String, Decimal]

Ceci est un exemple stupide, bien sûr. Le type de retour signifie "soit renvoyer une chaîne, soit un nombre décimal". Par convention, le type de gauche est le type d'erreur (dans ce cas, une chaîne avec le message d'erreur) et le type de droite est le type de résultat.

Ceci est similaire aux exceptions, sauf que les exceptions sont également des constructions de flux de contrôle. En fait, leur pouvoir expressif est équivalent à GOTO.

Jörg W Mittag
la source
méthode en Java renvoyant exception semble bizarre. " Retourne une exception " ... hmm
mord le
3
Une exception est l'un des résultats possibles d'une méthode en Java. En fait, j'ai oublié un quatrième type: Unit(une méthode n'est pas obligée de revenir du tout). Certes, vous n'utilisez pas le returnmot - clé, mais le résultat provient néanmoins de la méthode. À quelques exceptions près, il est même explicitement mentionné dans la signature de type.
Jörg W Mittag
Je vois. Votre argument semble avoir quelques mérites, mais la façon dont il est présenté ne le fait pas paraître convaincant ... Plutôt opposé
mordu le
4
Dans de nombreuses langues, les conditions d'erreur sont modélisées comme un sous-programme renvoyant un type de résultat ou un type d'erreur. Les exceptions correspondent à une idée similaire, à la différence qu’elles sont également des constructions de flux de contrôle (dont le pouvoir expressif est égal à GOTO, en réalité).
Jörg W Mittag Le
4

Aucune réponse n'a encore mentionné les principes SOLID. En particulier, vous devez suivre le principe de substitution de Liskov, à savoir que toute classe recevant un type autre que le type attendu peut toujours travailler avec ce qu'elle obtient, sans rien faire pour tester le type retourné.

Donc, si vous jetez des propriétés supplémentaires sur un objet, ou si vous encapsulez une fonction retournée avec une sorte de décorateur qui accomplit tout ce que la fonction d'origine était censée accomplir, vous êtes bon, tant qu'aucun code n'appelant votre fonction ne repose sur cette comportement, dans n'importe quel chemin de code.

Au lieu de renvoyer une chaîne ou un entier, un meilleur exemple pourrait être qu'il retourne un système d'arrosage ou un chat. C'est bien si tout le code d'appel est appelé, c'est d'appeler functionInQuestion.hiss (). Effectivement, vous avez une interface implicite attendue par le code appelant, et un langage à typage dynamique ne vous obligera pas à rendre l'interface explicite.

Malheureusement, vos collègues le feront probablement, vous devrez donc probablement faire le même travail dans votre documentation, sauf qu'il n'existe pas de moyen de le faire universellement accepté, concis, analysable par machine - comme il en existe quand vous définissez une interface. dans une langue qui les a.

psr
la source
3

Le seul endroit où je me vois envoyer différents types concerne les entrées non valides ou les "exceptions de pauvres" où la condition "exceptionnelle" n'est pas très exceptionnelle. Par exemple, à partir de mon référentiel de fonctions utilitaires PHP, voici un exemple abrégé:

function ensure_fields($consideration)
{
        $args = func_get_args();
        foreach ( $args as $a ) {
                if ( !is_string($a) ) {
                        return NULL;
                }
                if ( !isset($consideration[$a]) || $consideration[$a]=='' ) {
                        return FALSE;
                }
        }

        return TRUE;
}

La fonction renvoie nominalement un BOOLEAN, cependant elle renvoie NULL si une entrée est invalide. Notez que depuis PHP 5.3, toutes les fonctions internes à PHP se comportent également de cette manière . De plus, certaines fonctions internes à PHP renvoient FALSE ou INT en entrée nominale, voir:

strpos('Hello', 'e');  // Returns INT(1)
strpos('Hello', 'q');  // Returns BOOL(FALSE)
dotancohen
la source
4
Et c'est pourquoi je déteste PHP. Eh bien, une des raisons, de toute façon.
Aaronaught
1
Un vote négatif parce que quelqu'un n'aime pas le langage que je développe professionnellement?!? Attendez qu'ils sachent que je suis juif! :)
dotancohen
3
Ne présumez pas toujours que les personnes qui commentent et les personnes qui votent vers le bas sont les mêmes.
Aaronaught
1
Je préfère avoir une exception au lieu de null, parce que (a) elle échoue bruyamment , elle est donc plus susceptible d'être fixée sur la phase de développement / test, l’application entière (dans mon contrôleur frontal) et enregistrez-la simplement (j’envoie généralement des e-mails à l’équipe de développement), afin qu’elle puisse être corrigée ultérieurement. Et je déteste réellement que la bibliothèque standard PHP utilise principalement l'approche "return null" - elle rend le code PHP plus sujet aux erreurs, à moins que vous ne vérifiiez tout isset(), ce qui est trop lourd.
scriptin
1
De plus, si vous revenez nullen cas d'erreur, veuillez l'indiquer clairement dans un docblock (par exemple @return boolean|null). Ainsi, si je rencontrais votre code un jour, je n'aurais pas à vérifier le corps de la fonction / méthode.
scriptin
3

Je ne pense pas que ce soit une mauvaise idée! Contrairement à cette opinion très répandue, et déjà soulignée par Robert Harvey, les langages à typage statique comme Java ont introduit Generics exactement pour des situations comme celle que vous demandez. En fait, Java essaie de conserver (dans la mesure du possible) la sécurité typographique au moment de la compilation, et parfois, Generics évite la duplication de code, pourquoi? Parce que vous pouvez écrire la même méthode ou la même classe qui gère / renvoie des types différents . Je vais juste donner un très bref exemple pour montrer cette idée:

Java 1.4

public static Boolean getBoolean(String property){
    return (Boolean) properties.getProperty(property);
}
public static Integer getInt(String property){
    return (Integer) properties.getProperty(property);
}

Java 1.5+

public static <T> getValue(String property, Class<T> clazz) throws WhateverCheckedException{
    return clazz.getConstructor(String.class).newInstance(properties.getProperty(property));
}
//the call will be
Boolean b = getValue("useProxy",Boolean.class);
Integer b = getValue("proxyPort",Integer.class);

Dans un langage typé dynamique, étant donné que vous n'avez aucune sécurité à la compilation lors de la compilation, vous êtes absolument libre d'écrire le même code qui fonctionne pour plusieurs types. Étant donné que même dans un langage à typage statique, des génériques ont été introduits pour résoudre ce problème, il est clair que l'écriture d'une fonction qui renvoie différents types dans un langage dynamique n'est pas une mauvaise idée.

thermz
la source
Un autre downvote sans explication! Merci la communauté SE!
thermz
2
Ayez une sympathie +1. Vous devriez essayer d’ouvrir votre réponse avec une réponse plus claire et plus directe et de ne pas commenter les différentes réponses. (Je vous donnerais un autre +1, puisque je suis d'accord avec vous, mais je n'ai qu'un seul à donner.)
DougM
3
Les génériques n'existent pas dans les langages à typage dynamique (à ma connaissance). Ils n'auraient aucune raison de le faire. La plupart des génériques constituent un cas particulier, où le "conteneur" est plus intéressant que ce qu'il contient (c'est pourquoi certaines langues, comme Java, utilisent en réalité le type effacement). Le type générique est en réalité un type qui lui est propre. Les méthodes génériques , sans type générique réel, comme dans votre exemple ici, ne sont presque toujours que du sucre syntaxique pour une sémantique assign-and-cast. Ce n’est pas un exemple particulièrement convaincant de l’objet de la question.
Aaronaught
1
J'essaie d'être un peu plus synthétique: "Même dans un langage à typage statique, des génériques ont été introduits pour résoudre ce problème, il est clair que l'écriture d'une fonction qui renvoie différents types dans un langage dynamique n'est pas une mauvaise idée." Mieux maintenant? Vérifiez la réponse de Robert Harvey ou le commentaire de BRPocock et vous vous rendrez compte que l'exemple de Generics est lié à cette question
thermz
1
Ne te sens pas mal, @thermz. Les votes négatifs en disent souvent plus sur le lecteur que sur l'écrivain.
Karl Bielefeldt
2

Développer un logiciel est fondamentalement un art et un art de gérer la complexité. Vous essayez de restreindre le système aux points où vous pouvez vous le permettre et de limiter les options à d’autres points. L'interface de Function est un contrat qui permet de gérer la complexité du code en limitant les connaissances requises pour travailler avec n'importe quel élément de code. En renvoyant différents types, vous développez de manière significative l'interface de la fonction en y ajoutant toutes les interfaces de types différents que vous renvoyez ET en ajoutant des règles non évidentes indiquant l'interface à renvoyer.

Kaerber
la source
1

Perl l'utilise beaucoup car ce que fait une fonction dépend de son contexte . Par exemple, une fonction peut renvoyer un tableau si elle est utilisée dans un contexte de liste ou la longueur du tableau si elle est utilisée à un endroit où une valeur scalaire est attendue. Dans le didacticiel correspondant au premier résultat relatif à "contexte Perl" , procédez comme suit:

my @now = localtime();

Alors @now est une variable de tableau (c'est ce que signifie @) et contiendra un tableau comme (40, 51, 20, 9, 0, 109, 5, 8, 0).

Si, au lieu de cela, vous appelez la fonction de manière à ce que son résultat soit un scalaire, avec ($ les variables sont des scalaires):

my $now = localtime();

alors cela fait quelque chose de complètement différent: $ now sera quelque chose comme "Fri Jan 9 20:51:40 2009".

Un autre exemple auquel je peux penser est l’implémentation d’API REST, où le format de ce qui est renvoyé dépend de ce que le client veut. Par exemple, HTML ou JSON ou XML. Bien que techniquement ce soient tous des flux d'octets, l'idée est similaire.

RemcoGerlich
la source
Vous voudrez peut-être mentionner la manière spécifique dont il fait cela en perl - wantarray ... et peut-être un lien vers une discussion si c'est bon ou mauvais - Utilisation de wantarray considérée comme nocive chez PerlMonks
Par coïncidence, je traite de cela avec une API Java Rest que je construis. J'ai rendu possible pour une ressource de renvoyer XML ou JSON. Le type de dénominateur commun le plus bas dans ce cas est a String. Maintenant, les gens demandent de la documentation sur le type de retour. Les outils java peuvent le générer automatiquement si je retourne une classe, mais parce que j'ai choisi, Stringje ne peux pas générer la documentation automatiquement. Avec le recul / la moralité de l’histoire, j’aimerais retourner un type par méthode.
Daniel Kaplan le
Strictement, cela équivaut à une forme de surcharge. En d'autres termes, il existe plusieurs fonctions fédérées et, en fonction des particularités de l'appel, l'une ou l'autre version est choisie.
Ian
1

Dans Dynamic-Land, tout est axé sur la frappe de canards. La chose la plus responsable exposée / confrontée au public consiste à envelopper des types potentiellement différents dans un wrapper qui leur donne la même interface.

function ThingyWrapper(thingy){ //a function constructor (class-like thingy)

    //thingy is effectively private and persistent for ThingyWrapper instances

    if(typeof thingy === 'array'){
        this.alertItems = function(){
            thingy.forEach(function(el){ alert(el); });
        }
    }
    else {
        this.alertItems = function(){
            for(var x in thingy){ alert(thingy[x]); }
        }
    }
}

function gimmeThingy(){
    var
        coinToss = Math.round( Math.random() ),//gives me 0 or 1
        arrayThingy = [1,2,3],
        objectThingy = { item1:1, item2:2, item3:3 }
    ;

    //0 dynamically evaluates to false in JS
    return new ThingyWrapper( coinToss ? arrayThingy : objectThingy );
}

gimmeThingy().alertItems(); //should be same every time except order of numbers - maybe

Il peut parfois être judicieux de casser différents types sans utiliser de wrapper générique, mais honnêtement, sept ans après la rédaction de JS, ce n’est pas quelque chose que j’ai trouvé raisonnable ou pratique de le faire très souvent. C'est ce que je ferais principalement dans le contexte d'environnements fermés, comme l'intérieur d'un objet où les choses sont en train de se mettre en place. Mais ce n’est pas quelque chose que j’ai fait assez souvent que l’on se méfie des exemples.

Surtout, je vous conseillerais de cesser de penser aux types autant. Vous travaillez avec des types lorsque vous en avez besoin dans un langage dynamique. Pas plus. C'est le point entier. Ne vérifiez pas le type de chaque argument. C'est quelque chose que je ne serais tenté de faire que dans un environnement où la même méthode pourrait donner des résultats incohérents de manière non évidente (donc ne faites absolument pas ce genre de chose). Mais ce n'est pas le type qui compte, c'est la façon dont vous me donnez le produit que vous donnez.

Erik Reppen
la source