Il y a quelque temps, j'ai été réprimandé par Simon Urbanek de l'équipe de base R (je crois) pour avoir recommandé à un utilisateur d'appeler explicitement return
à la fin d'une fonction (son commentaire a cependant été supprimé):
foo = function() {
return(value)
}
il a plutôt recommandé:
foo = function() {
value
}
Dans une situation comme celle-ci, il est probablement nécessaire:
foo = function() {
if(a) {
return(a)
} else {
return(b)
}
}
Son commentaire a permis de comprendre pourquoi ne pas appeler, return
sauf si cela était strictement nécessaire, est une bonne chose, mais cela a été supprimé.
Ma question est: pourquoi n'appelle pas return
plus vite ou mieux, et donc préférable?
return
est inutile même dans le dernier exemple. La suppressionreturn
peut accélérer un peu, mais à mon avis, c'est parce que R est dit être un langage de programmation fonctionnel.return
induit un saut non local, et le saut non local explicite est inhabituel pour FP. En fait, par exemple, le régime n'a pasreturn
. Je pense que mes commentaires sont trop courts (et peut-être incorrects) comme réponse.return
,break
,continue
soit, ce qui est parfois fastidieux.Réponses:
La question était: pourquoi n'appeler pas (explicitement) le retour plus rapidement ou mieux, et donc préférable?
Il n'y a aucune déclaration dans la documentation R faisant une telle hypothèse.
La page principale? 'Fonction' dit:
Est-ce plus rapide sans appeler le retour?
Les deux
function()
etreturn()
sont des fonctions primitives etfunction()
se retourne la dernière valeur évaluée même sans inclure lareturn()
fonction.Appeler
return()
comme.Primitive('return')
avec cette dernière valeur comme argument fera le même travail mais aura besoin d'un appel de plus. Pour que cet.Primitive('return')
appel (souvent) inutile puisse attirer des ressources supplémentaires. Une mesure simple montre cependant que la différence résultante est très faible et ne peut donc pas être la raison pour ne pas utiliser de retour explicite. Le tracé suivant est créé à partir des données sélectionnées de cette façon:L'image ci-dessus peut légèrement différer sur votre plate-forme. Sur la base des données mesurées, la taille de l'objet retourné ne cause aucune différence, le nombre de répétitions (même s'il est agrandi) ne fait qu'une très petite différence, ce qui en vrai mot avec des données réelles et un algorithme réel ne pourrait pas être compté ou faire votre le script s'exécute plus rapidement.
Est-ce mieux sans appeler le retour?
Return
est un bon outil pour concevoir clairement des "feuilles" de code où la routine doit se terminer, sortir de la fonction et retourner la valeur.Cela dépend de la stratégie et du style de programmation du programmeur quel style il utilise, il ne peut utiliser aucun return () car il n'est pas requis.
Les programmeurs principaux de R utilisent les deux approches, c.-à-d. avec et sans retour explicite () comme il est possible de trouver dans les sources des fonctions «de base».
Plusieurs fois, seul return () est utilisé (sans argument) retournant NULL dans les cas pour arrêter conditionnellement la fonction.
Il n'est pas clair si c'est mieux ou non car l'utilisateur standard ou l'analyste utilisant R ne peut pas voir la vraie différence.
Mon avis est que la question devrait être: Y a - t-il un danger à utiliser un retour explicite provenant de la mise en œuvre de R?
Ou, peut-être mieux, l'écriture de code de fonction par l'utilisateur devrait toujours demander: Quel est l'effet de ne pas utiliser de retour explicite (ou de placer l'objet à renvoyer comme dernière feuille de branche de code) dans le code de fonction?
la source
return
, et cela revient à la préférence du programmeur de l'utiliser ou non.return
est vraiment la dernière chose dont vous devriez vous inquiéter.return
appels de fonction inutiles . La question que vous devriez poser n'est pas celle que vous proposez à la fin. Au lieu de cela, c'est: «pourquoi devrais- je utiliser un redondantreturn
? Quel avantage cela apporte-t-il? " En fin de compte, la réponse est «pas grand-chose», voire «pas du tout». Votre réponse ne l'apprécie pas.return
est comme un commentaire explicite qui dit "incrémenter x de 1", à côté d'un morceau de codex = x + 2
. En d'autres termes, son explicitation est (a) tout à fait hors de propos et (b) elle véhicule des informations erronées . Parce quereturn
la sémantique de R est, purement, «abandonner cette fonction». Cela ne signifie pas la même chose quereturn
dans d'autres langues.Si tout le monde convient que
return
n'est pas nécessaire à la fin du corps d'une fonctionreturn
est légèrement plus rapide (selon le test de @ Alan, 4,3 microsecondes contre 5,1)devrions-nous tous cesser d'utiliser
return
à la fin d'une fonction? Je ne le ferai certainement pas, et j'aimerais expliquer pourquoi. J'espère entendre si d'autres personnes partagent mon opinion. Et je m'excuse si ce n'est pas une réponse directe au PO, mais plutôt un long commentaire subjectif.Mon principal problème avec la non-utilisation
return
est que, comme l'a souligné Paul, il existe d'autres endroits dans le corps d'une fonction où vous pouvez en avoir besoin. Et si vous êtes obligé d'utiliserreturn
quelque part au milieu de votre fonction, pourquoi ne pas rendre toutes lesreturn
instructions explicites? Je déteste être incohérent. Je pense aussi que le code se lit mieux; on peut scanner la fonction et voir facilement tous les points et valeurs de sortie.Paul a utilisé cet exemple:
Malheureusement, on pourrait souligner qu'il peut facilement être réécrit comme:
Cette dernière version est même conforme à certaines normes de codage de programmation qui préconisent une déclaration de retour par fonction. Je pense qu'un meilleur exemple aurait pu être:
Ce serait beaucoup plus difficile à réécrire en utilisant une seule instruction de retour: il faudrait plusieurs
break
s et un système complexe de variables booléennes pour les propager. Tout cela pour dire que la règle du retour unique ne fonctionne pas bien avec R. Donc, si vous devez utiliserreturn
à certains endroits du corps de votre fonction, pourquoi ne pas être cohérent et l'utiliser partout?Je ne pense pas que l'argument de la vitesse soit valable. Une différence de 0,8 microseconde n'est rien lorsque vous commencez à regarder des fonctions qui font réellement quelque chose. La dernière chose que je peux voir, c'est que c'est moins de frappe mais bon, je ne suis pas paresseux.
la source
return
déclaration dans certains cas, comme l'a montré @flodel. Alternativement, il y a des situations où une déclaration de retour est mieux omise, par exemple beaucoup et beaucoup de petits appels de fonction. Dans tous les autres, disons 95%, des cas, peu importe que l'on utilisereturn
ou non, et cela revient à la préférence. J'aime utiliser le retour car il est plus explicite dans ce que vous voulez dire, donc plus lisible. Peut-être que cette discussion s'apparente à<-
vs=
?return
pour renvoyer une valeur est absurde, au même niveau que l'écritureif (x == TRUE)
au lieu deif (x)
.foo
tant quefoo <- function(x) if (a) a else b
(avec des sauts de ligne si nécessaire). Pas besoin de retour explicite ou de valeur intermédiaire.Ceci est une discussion intéressante. Je pense que l'exemple de @ flodel est excellent. Cependant, je pense que cela illustre mon argument (et @koshke le mentionne dans un commentaire) qui a du
return
sens lorsque vous utilisez un impératif au lieu d'un style de codage fonctionnel .Sans trop insister, mais j'aurais réécrit
foo
comme ceci:Un style fonctionnel évite les changements d'état, comme le stockage de la valeur de
output
. Dans ce style,return
n'est pas à sa place;foo
ressemble plus à une fonction mathématique.Je suis d'accord avec @flodel: utiliser un système complexe de variables booléennes dans
bar
serait moins clair et inutile quand vous l'avezreturn
. Ce qui rendbar
si accessible auxreturn
déclarations, c'est qu'il est écrit dans un style impératif. En effet, les variables booléennes représentent les changements "d'état" évités dans un style fonctionnel.Il est vraiment difficile de réécrire
bar
dans un style fonctionnel, car il ne s'agit que de pseudocode, mais l'idée est quelque chose comme ceci:La
while
boucle serait la plus difficile à réécrire, car elle est contrôlée par les changements d'état dea
.La perte de vitesse causée par un appel à
return
est négligeable, mais l'efficacité obtenue en évitantreturn
et en réécrivant dans un style fonctionnel est souvent énorme. Dire aux nouveaux utilisateurs d'arrêter d'utiliserreturn
ne sera probablement pas utile, mais les guider vers un style fonctionnel sera payant.@Paul
return
est nécessaire dans un style impératif car vous voulez souvent quitter la fonction à différents points d'une boucle. Un style fonctionnel n'utilise pas de boucles et n'a donc pas besoinreturn
. Dans un style purement fonctionnel, l'appel final est presque toujours la valeur de retour souhaitée.En Python, les fonctions nécessitent une
return
instruction. Cependant, si vous avez programmé votre fonction dans un style fonctionnel, vous n'aurez probablement qu'une seulereturn
instruction: à la fin de votre fonction.En utilisant un exemple d'un autre article StackOverflow, disons que nous voulions une fonction qui retournait
TRUE
si toutes les valeurs d'une donnéex
avaient une longueur impaire. Nous pourrions utiliser deux styles:Dans un style fonctionnel, la valeur à renvoyer tombe naturellement aux extrémités de la fonction. Encore une fois, cela ressemble plus à une fonction mathématique.
@GSee Les avertissements décrits dans
?ifelse
sont certainement intéressants, mais je ne pense pas qu'ils essaient de dissuader l'utilisation de la fonction. En fait,ifelse
a l'avantage de vectoriser automatiquement les fonctions. Par exemple, considérons une version légèrement modifiée defoo
:Cette fonction fonctionne bien quand
length(a)
est 1. Mais si vous avez réécritfoo
avec unifelse
Fonctionne maintenant
foo
sur n'importe quelle longueur dea
. En fait, cela fonctionnerait même quanda
est une matrice. Renvoyer une valeur de la même forme qu'unetest
fonctionnalité qui aide à la vectorisation, pas un problème.la source
return
ne correspond pas à un style de programmation fonctionnel. Que l'on programme impérativement ou fonctionnellement, à un certain stade, une fonction ou un sous-programme doit renvoyer quelque chose. Par exemple, la programmation fonctionnelle en python nécessite toujours unereturn
instruction. Pourriez-vous élaborer davantage sur ce point.ifelse(a,a,b)
est une bête noire. Il semble que chaque ligne?ifelse
crie, "ne m'utilisez pas à la place deif (a) {a} else b
". par exemple "... retourne une valeur avec la même forme quetest
", "siyes
ouno
sont trop courts, leurs éléments sont recyclés.", "le mode du résultat peut dépendre de la valeur detest
", "l'attribut class du résultat est tiré detest
et peut être inapproprié pour les valeurs sélectionnées à partir deyes
etno
"foo
n'a pas beaucoup de sens; il retournera toujours TRUE oub
.ifelse
Son utilisation renverra 1 ou plusieurs VRAIS et / ou 1 ou plusieursb
s. Au départ, je pensais que l'intention de la fonction était de dire "si une déclaration est VRAIE, retourne quelque chose, sinon, retourne autre chose." Je ne pense pas que cela devrait être vectorisé, car cela deviendrait alors "retourner les éléments d'un objet qui sont VRAIS, et pour tous les éléments qui ne sont pas VRAIS, retournerb
.Il semble que sans
return()
c'est plus rapide ...____ MODIFIER__ _ __ _ __ _ __ _ __ _ ___
Je passe à d'autres benchmark (
benchmark(fuu(x),foo(x),replications=1e7)
) et le résultat est inversé ... Je vais essayer sur un serveur.la source
return()
, un si vous ne le faites pas. Il est totalement redondant à la fin d'une fonction carfunction()
renvoie sa dernière valeur. Vous ne le remarquerez que dans de nombreuses répétitions d'une fonction où peu de choses sont faites en interne, de sorte que le coût dereturn()
devient une grande partie du temps de calcul total de la fonction.Un problème avec ne pas mettre explicitement «return» à la fin est que si l'on ajoute des instructions supplémentaires à la fin de la méthode, la valeur de retour est soudainement erronée:
Cela renvoie la valeur de
dosomething()
.Maintenant, nous arrivons le lendemain et ajoutons une nouvelle ligne:
Nous voulions que notre code renvoie la valeur de
dosomething()
, mais ce n'est plus le cas.Avec un retour explicite, cela devient vraiment évident:
Nous pouvons voir qu'il y a quelque chose d'étrange dans ce code et le corriger:
la source
return
n'est pas un raccourci, c'est le style approprié dans la programmation fonctionnelle. L'utilisation d'return
appels de fonctions inutiles est un exemple de programmation culte du fret .return
déclaration et de ne pas remarquer qu'elle ne sera pas exécutée. Vous pouvez tout aussi bien ajouter un commentaire après la valeur que vous souhaitez renvoyer, par exempledosomething() # this is my return value, don't add anything after it unless you know goddam well what you are doing
C'est plus rapide car
return
c'est une fonction (primitive) dans R, ce qui signifie que son utilisation dans le code entraîne le coût d'un appel de fonction. Comparez cela à la plupart des autres langages de programmation, où sereturn
trouve un mot-clé, mais pas un appel de fonction: il ne se traduit par aucune exécution de code d'exécution.Cela dit, appeler une fonction primitive de cette manière est assez rapide dans R, et appeler
return
entraîne une surcharge minuscule. Ce n'est pas l'argument pour omettrereturn
.Parce qu'il n'y a aucune raison de l' utiliser.
Parce qu'il est redondant et qu'il n'ajoute pas de redondance utile .
Pour être clair: la redondance peut parfois être utile . Mais la plupart des redondances ne sont pas de ce type. Au lieu de cela, c'est du genre qui ajoute un encombrement visuel sans ajouter d'informations: c'est l'équivalent de programmation d'un mot de remplissage ou d'un graphique indésirable ).
Prenons l'exemple suivant d'un commentaire explicatif, universellement reconnu comme une mauvaise redondance car le commentaire paraphrase simplement ce que le code exprime déjà:
L'utilisation
return
de R appartient à la même catégorie, car R est un langage de programmation fonctionnel et, dans R, chaque appel de fonction a une valeur . C'est une propriété fondamentale de R. Et une fois que vous voyez le code R du point de vue que chaque expression (y compris chaque appel de fonction) a une valeur, la question devient alors: "pourquoi devrais- je utiliserreturn
?" Il doit y avoir une raison positive, car la valeur par défaut est de ne pas l'utiliser.L'une de ces raisons positives est de signaler la sortie anticipée d'une fonction, par exemple dans une clause de garde :
Il s'agit d'une utilisation valide et non redondante de
return
. Cependant, de telles clauses de garde sont rares dans R par rapport à d'autres langages, et puisque chaque expression a une valeur, un régulierif
ne nécessite pasreturn
:On peut même réécrire
f
comme ceci:… Où
if (cond) expr
est le même queif (cond) expr else NULL
.Enfin, je voudrais prévenir trois objections courantes:
Certaines personnes soutiennent que l'utilisation
return
ajoute de la clarté, car elle signale «cette fonction renvoie une valeur». Mais comme expliqué ci-dessus, chaque fonction renvoie quelque chose dans R. Penserreturn
à un marqueur de retour d'une valeur n'est pas seulement redondant, il est activement trompeur .De même, le Zen of Python a une merveilleuse ligne directrice qui doit toujours être suivie:
Comment la suppression de redondance
return
ne viole-t-elle pas cela? Parce que la valeur de retour d'une fonction dans un langage fonctionnel est toujours explicite: c'est sa dernière expression. Il s'agit là encore du même argument entre explicitation et redondance.En fait, si vous voulez une explication, utilisez-la pour mettre en évidence l'exception à la règle: marquez les fonctions qui ne renvoient pas de valeur significative, qui ne sont appelées que pour leurs effets secondaires (comme
cat
). À l' exception R a un meilleur marqueur quereturn
pour ce cas:invisible
. Par exemple, j'écriraisMais qu'en est-il des fonctions longues? Ne sera-t-il pas facile de perdre la trace de ce qui est retourné?
Deux réponses: d'abord, pas vraiment. La règle est claire: la dernière expression d'une fonction est sa valeur. Il n'y a rien à suivre.
Mais plus important encore, le problème des fonctions longues n'est pas le manque de
return
marqueurs explicites . C'est la durée de la fonction . Les fonctions longues presque (?) Violent toujours le principe de la responsabilité unique et même lorsqu'elles ne le font pas, elles bénéficieront d'être éclatées pour plus de lisibilité.la source
return
pour le rendre plus similaire à d'autres langues. Mais c'est un mauvais argument: les autres langages de programmation fonctionnels ont tendance à ne pas utiliserreturn
non plus. Ce ne sont que les langages impératifs , où toutes les expressions n'ont pas de valeur, qui l'utilisent.return
supports est explicitement meilleure et j'ai lu votre réponse avec toutes les critiques. Votre réponse m'a amené à réfléchir à ce point de vue. Je pense que le besoin d'utiliserreturn
explicitement (au moins dans mon cas), est lié à un besoin de mieux pouvoir réviser mes fonctions à un moment ultérieur. Avec l'idée que mes fonctions pourraient simplement être trop complexes, je peux maintenant voir qu'un objectif pour améliorer mon style de programmation, serait de s'efforcer de structurer les codes pour maintenir une explicité sans areturn
. Merci pour ces réflexions et cette perspicacité !!Je pense
return
que c'est un truc. En règle générale, la valeur de la dernière expression évaluée dans une fonction devient la valeur de la fonction - et ce modèle général se trouve à de nombreux endroits. Tous les éléments suivants sont évalués à 3:Ce qui
return
ne revient pas vraiment à renvoyer une valeur (cela se fait avec ou sans elle) mais à "casser" la fonction de manière irrégulière. En ce sens, c'est l'équivalent le plus proche de l'instruction GOTO dans R (il y a aussi break et next). J'utilisereturn
très rarement et jamais à la fin d'une fonction.... cela peut être réécrit comme
if(a) a else b
ce qui est beaucoup mieux lisible et moins entre crochets. Pas besoin dureturn
tout ici. Mon cas prototype d'utilisation de "retour" serait quelque chose comme ...Généralement, la nécessité de nombreux retours suggère que le problème est soit laid soit mal structuré.
<>
return
n'a pas vraiment besoin d'une fonction pour fonctionner: vous pouvez l'utiliser pour sortir d'un ensemble d'expressions à évaluer.la source
return
(mon laid exemple ci-dessus est très artificiel): supposons que vous ayez besoin de tester si une valeur estNULL
ouNA
: dans ces cas, retournez une chaîne vide, sinon retournez lacharacter
valeur. Mais un test deis.na(NULL)
donne une erreur, il semble donc que cela ne peut être fait qu'avecif(is.null(x)) return("")
, puis continuerif(is.na(x)) .....
. (On peut utiliser à lalength(x)==0
place deis.null(x)
mais tout de même, il n'est pas possible d'utiliserlength(x)==0 | is.na(x)
six
c'est le casNULL
.)|
(OU vectorisé où les deux côtés sont évalués) au lieu de||
(OU court-circuit, non vectorisé, où les prédicats sont évalués à leur tour). Considérezif (TRUE | stop()) print(1)
versusif (TRUE || stop()) print(1)
return
peut augmenter la lisibilité du code:la source
foo <- function() a || b
(ce qui est plus lisible de l'OMI; en tout cas, il n'y a pas de lisibilité "pure" mais de lisibilité de l'avis de quelqu'un: il y a des gens qui disent que le langage d'assemblage est parfaitement lisible)L'argument de la redondance a beaucoup été soulevé ici. À mon avis, ce n'est pas une raison suffisante pour omettre
return()
. La redondance n'est pas automatiquement une mauvaise chose. Lorsqu'elle est utilisée stratégiquement, la redondance rend le code plus clair et plus maintenable.Prenons cet exemple: les paramètres de fonction ont souvent des valeurs par défaut. La spécification d'une valeur identique à celle par défaut est donc redondante. Sauf que cela rend évident le comportement que j'attends. Pas besoin de remonter la page de manuel des fonctions pour me rappeler quelles sont les valeurs par défaut. Et ne vous inquiétez pas d'une future version de la fonction qui changera ses valeurs par défaut.
Avec une pénalité de performance négligeable pour les appels
return()
(selon les références publiées ici par d'autres), il s'agit de style plutôt que de bien et de mal. Pour que quelque chose soit "mal", il doit y avoir un désavantage clair, et personne ici n'a démontré de façon satisfaisante que l'inclusion ou l'omissionreturn()
a un désavantage constant. Cela semble très spécifique à chaque cas et à l'utilisateur.Voici donc où j'en suis.
Je suis mal à l'aise avec les variables "orphelines" comme dans l'exemple ci-dessus. Allait
abcd
faire partie d'une déclaration que je n'ai pas fini d'écrire? Est-ce un vestige d'une épissure / modification dans mon code et doit être supprimé? Ai-je accidentellement collé / déplacé quelque chose ailleurs?En revanche, ce deuxième exemple me montre clairement qu'il s'agit d'une valeur de retour prévue, plutôt que d'un accident ou d'un code incomplet. Pour moi, cette redondance n'est absolument pas inutile.
Bien sûr, une fois la fonction terminée et opérationnelle, je pourrais supprimer le retour. Mais sa suppression est en soi une étape supplémentaire redondante, et à mon avis plus inutile que l'inclusion
return()
en premier lieu.Cela dit, je n'utilise pas de fonctions
return()
à une ligne sans nom. Là, il représente une grande partie du code de la fonction et provoque donc principalement un encombrement visuel qui rend le code moins lisible. Mais pour de plus grandes fonctions formellement définies et nommées, je l'utilise et je continuerai probablement à le faire.la source
return()
en R ne coûte rien. C'est objectivement redondant, mais être "inutile" est votre jugement subjectif. Redondant et inutile ne sont pas nécessairement synonymes. C'est là que nous ne sommes pas d'accord.return
, et même si je ne suis pas convaincu, je pense que c'est potentiellement valide (c'est certainement dans un langage impératif ... ma conviction est que cela ne se traduit pas en langages fonctionnels).