Appel explicite de retour dans une fonction ou non


199

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, returnsauf si cela était strictement nécessaire, est une bonne chose, mais cela a été supprimé.

Ma question est: pourquoi n'appelle pas returnplus vite ou mieux, et donc préférable?


12
returnest inutile même dans le dernier exemple. La suppression returnpeut accélérer un peu, mais à mon avis, c'est parce que R est dit être un langage de programmation fonctionnel.
kohske

4
@kohske Pourriez-vous développer votre commentaire en une réponse, y compris plus de détails sur la raison pour laquelle il est plus rapide et comment cela est lié au fait que R est un langage de programmation fonctionnel?
Paul Hiemstra

2
returninduit un saut non local, et le saut non local explicite est inhabituel pour FP. En fait, par exemple, le régime n'a pas return. Je pense que mes commentaires sont trop courts (et peut-être incorrects) comme réponse.
kohske

2
F # ne pas return, break, continuesoit, ce qui est parfois fastidieux.
colinfang

Réponses:


129

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:

function( arglist ) expr
return(value)

Est-ce plus rapide sans appeler le retour?

Les deux function()et return()sont des fonctions primitives et function()se retourne la dernière valeur évaluée même sans inclure la return()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:

bench_nor2 <- function(x,repeats) { system.time(rep(
# without explicit return
(function(x) vector(length=x,mode="numeric"))(x)
,repeats)) }

bench_ret2 <- function(x,repeats) { system.time(rep(
# with explicit return
(function(x) return(vector(length=x,mode="numeric")))(x)
,repeats)) }

maxlen <- 1000
reps <- 10000
along <- seq(from=1,to=maxlen,by=5)
ret <- sapply(along,FUN=bench_ret2,repeats=reps)
nor <- sapply(along,FUN=bench_nor2,repeats=reps)
res <- data.frame(N=along,ELAPSED_RET=ret["elapsed",],ELAPSED_NOR=nor["elapsed",])

# res object is then visualized
# R version 2.15

Fonction comparaison du temps écoulé

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.

# here without calling .Primitive('return')
> (function() {10;20;30;40})()
[1] 40
# here with .Primitive('return')
> (function() {10;20;30;40;return(40)})()
[1] 40
# here return terminates flow
> (function() {10;20;return();30;40})()
NULL
> (function() {10;20;return(25);30;40})()
[1] 25
> 

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?


4
Merci pour une très bonne réponse. Je crois qu'il n'y a aucun danger à l'utiliser return, et cela revient à la préférence du programmeur de l'utiliser ou non.
Paul Hiemstra

38
La vitesse returnest vraiment la dernière chose dont vous devriez vous inquiéter.
hadley

2
Je pense que c'est une mauvaise réponse. Les raisons se résument à un désaccord fondamental sur la valeur de l'utilisation d' returnappels 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 redondant return? 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.
Konrad Rudolph

@ KonradRudolph ... vous avez répété en fait ce que Paul demandait à l'origine (pourquoi le retour explicite est mauvais). Et je voudrais aussi savoir la bonne réponse (celle qui convient à tous) :). Envisagez-vous de fournir votre explication aux utilisateurs de ce site?
Petr Matousu

1
@Dason J'ai lié ailleurs un article Reddit expliquant pourquoi cet argument est défectueux dans ce contexte. Malheureusement, le commentaire semble être automatiquement supprimé à chaque fois. La version courte returnest comme un commentaire explicite qui dit "incrémenter x de 1", à côté d'un morceau de code x = 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 que returnla sémantique de R est, purement, «abandonner cette fonction». Cela ne signifie pas la même chose que returndans d'autres langues.
Konrad Rudolph

103

Si tout le monde convient que

  1. return n'est pas nécessaire à la fin du corps d'une fonction
  2. ne pas utiliser returnest 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 returnest 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'utiliser returnquelque part au milieu de votre fonction, pourquoi ne pas rendre toutes les returninstructions 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:

foo = function() {
 if(a) {
   return(a)
 } else {
   return(b)
 }
}

Malheureusement, on pourrait souligner qu'il peut facilement être réécrit comme:

foo = function() {
 if(a) {
   output <- a
 } else {
   output <- b
 }
output
}

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:

bar <- function() {
   while (a) {
      do_stuff
      for (b) {
         do_stuff
         if (c) return(1)
         for (d) {
            do_stuff
            if (e) return(2)
         }
      }
   }
   return(3)
}

Ce serait beaucoup plus difficile à réécrire en utilisant une seule instruction de retour: il faudrait plusieurs breaks 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 utiliser returnà 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.


7
+1, il existe un besoin évident de la returndé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 utilise returnou 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 =?
Paul Hiemstra

7
C'est traiter R comme un langage de programmation impératif, ce qui n'est pas le cas: c'est un langage de programmation fonctionnel. La programmation fonctionnelle fonctionne simplement différemment, et l'utilisation returnpour renvoyer une valeur est absurde, au même niveau que l'écriture if (x == TRUE)au lieu de if (x).
Konrad Rudolph

4
Vous réécrivez également en footant que foo <- 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.
hadley

26

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 returnsens lorsque vous utilisez un impératif au lieu d'un style de codage fonctionnel .

Sans trop insister, mais j'aurais réécrit foocomme ceci:

foo = function() ifelse(a,a,b)

Un style fonctionnel évite les changements d'état, comme le stockage de la valeur de output. Dans ce style, returnn'est pas à sa place; fooressemble plus à une fonction mathématique.

Je suis d'accord avec @flodel: utiliser un système complexe de variables booléennes dans barserait moins clair et inutile quand vous l'avez return. Ce qui rend barsi accessible aux returndé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 bardans un style fonctionnel, car il ne s'agit que de pseudocode, mais l'idée est quelque chose comme ceci:

e_func <- function() do_stuff
d_func <- function() ifelse(any(sapply(seq(d),e_func)),2,3)
b_func <- function() {
  do_stuff
  ifelse(c,1,sapply(seq(b),d_func))
}

bar <- function () {
   do_stuff
   sapply(seq(a),b_func) # Not exactly correct, but illustrates the idea.
}

La whileboucle serait la plus difficile à réécrire, car elle est contrôlée par les changements d'état de a.

La perte de vitesse causée par un appel à returnest négligeable, mais l'efficacité obtenue en évitant returnet en réécrivant dans un style fonctionnel est souvent énorme. Dire aux nouveaux utilisateurs d'arrêter d'utiliser returnne sera probablement pas utile, mais les guider vers un style fonctionnel sera payant.


@Paul returnest 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 besoin return. Dans un style purement fonctionnel, l'appel final est presque toujours la valeur de retour souhaitée.

En Python, les fonctions nécessitent une returninstruction. Cependant, si vous avez programmé votre fonction dans un style fonctionnel, vous n'aurez probablement qu'une seule returninstruction: à la fin de votre fonction.

En utilisant un exemple d'un autre article StackOverflow, disons que nous voulions une fonction qui retournait TRUEsi toutes les valeurs d'une donnée xavaient une longueur impaire. Nous pourrions utiliser deux styles:

# Procedural / Imperative
allOdd = function(x) {
  for (i in x) if (length(i) %% 2 == 0) return (FALSE)
  return (TRUE)
}

# Functional
allOdd = function(x) 
  all(length(x) %% 2 == 1)

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 ?ifelsesont certainement intéressants, mais je ne pense pas qu'ils essaient de dissuader l'utilisation de la fonction. En fait, ifelsea l'avantage de vectoriser automatiquement les fonctions. Par exemple, considérons une version légèrement modifiée de foo:

foo = function(a) { # Note that it now has an argument
 if(a) {
   return(a)
 } else {
   return(b)
 }
}

Cette fonction fonctionne bien quand length(a)est 1. Mais si vous avez réécrit fooavec unifelse

foo = function (a) ifelse(a,a,b)

Fonctionne maintenant foosur n'importe quelle longueur de a. En fait, cela fonctionnerait même quand aest une matrice. Renvoyer une valeur de la même forme qu'une testfonctionnalité qui aide à la vectorisation, pas un problème.


Je ne comprends pas pourquoi returnne 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 une returninstruction. Pourriez-vous élaborer davantage sur ce point.
Paul Hiemstra

Dans cette situation, l'utilisation ifelse(a,a,b)est une bête noire. Il semble que chaque ligne ?ifelsecrie, "ne m'utilisez pas à la place de if (a) {a} else b". par exemple "... retourne une valeur avec la même forme que test", "si yesou nosont trop courts, leurs éléments sont recyclés.", "le mode du résultat peut dépendre de la valeur de test", "l'attribut class du résultat est tiré de testet peut être inapproprié pour les valeurs sélectionnées à partir de yeset no"
GSee

Au deuxième regard, cela foon'a pas beaucoup de sens; il retournera toujours TRUE ou b. ifelseSon utilisation renverra 1 ou plusieurs VRAIS et / ou 1 ou plusieurs bs. 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, retourner b.
GSee

22

Il semble que sans return()c'est plus rapide ...

library(rbenchmark)
x <- 1
foo <- function(value) {
  return(value)
}
fuu <- function(value) {
  value
}
benchmark(foo(x),fuu(x),replications=1e7)
    test replications elapsed relative user.self sys.self user.child sys.child
1 foo(x)     10000000   51.36 1.185322     51.11     0.11          0         0
2 fuu(x)     10000000   43.33 1.000000     42.97     0.05          0         0

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


Pourriez-vous expliquer la raison pour laquelle cette différence se produit?
Paul Hiemstra

4
La réponse de @PaulHiemstra Petr couvre l'une des principales raisons à cela; deux appels lors de l'utilisation return(), un si vous ne le faites pas. Il est totalement redondant à la fin d'une fonction car function()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 de return()devient une grande partie du temps de calcul total de la fonction.
Gavin Simpson

13

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:

foo <- function() {
    dosomething()
}

Cela renvoie la valeur de dosomething().

Maintenant, nous arrivons le lendemain et ajoutons une nouvelle ligne:

foo <- function() {
    dosomething()
    dosomething2()
}

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:

foo <- function() {
    return( dosomething() )
    dosomething2()
}

Nous pouvons voir qu'il y a quelque chose d'étrange dans ce code et le corriger:

foo <- function() {
    dosomething2()
    return( dosomething() )
}

1
oui, en fait, je trouve qu'un return explicite () est utile lors du débogage; une fois le code nettoyé, son besoin est moins contraignant et je préfère l'élégance de ne pas l'avoir ...
PatrickT

Mais ce n'est pas réellement un problème dans le vrai code, c'est purement théorique. Le code qui pourrait en souffrir a un problème beaucoup plus important: un flux de code obscur et fragile qui est si peu évident que de simples ajouts le cassent.
Konrad Rudolph

@KonradRudolph Je pense que vous êtes en train de faire un No-True Scotsman dessus ;-) "Si c'est un problème dans votre code, vous êtes un mauvais programmeur!". Je ne suis pas vraiment d'accord. Je pense que même si vous pouvez vous passer de raccourcis sur de petits morceaux de code, où vous connaissez chaque ligne par cœur, cela reviendra vous mordre à mesure que votre code s'agrandit.
Hugh Perkins

2
@HughPerkins Ce n'est pas un vrai écossais ; il s'agit plutôt d'une observation empirique sur la complexité du code, appuyée par des décennies de bonnes pratiques en génie logiciel: garder les fonctions individuelles courtes et le flux de code évident. Et l'omission returnn'est pas un raccourci, c'est le style approprié dans la programmation fonctionnelle. L'utilisation d' returnappels de fonctions inutiles est un exemple de programmation culte du fret .
Konrad Rudolph

Eh bien ... je ne vois pas comment cela vous empêche d'ajouter quelque chose après votre returndé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
lebatsnok

10

Ma question est: pourquoi n'appelle pas returnplus vite

C'est plus rapide car returnc'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ù se returntrouve 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 returnentraîne une surcharge minuscule. Ce n'est pas l'argument pour omettre return.

ou mieux, et donc préférable?

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à:

# Add one to the result
result = x + 1

L'utilisation returnde 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 utiliser return?" 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 :

f = function (a, b) {
    if (! precondition(a)) return() # same as `return(NULL)`!
    calculation(b)
}

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égulier ifne nécessite pas return:

sign = function (num) {
    if (num > 0) {
        1
    } else if (num < 0) {
        -1
    } else {
        0
    }
}

On peut même réécrire fcomme ceci:

f = function (a, b) {
    if (precondition(a)) calculation(b)
}

… Où if (cond) exprest le même que if (cond) expr else NULL.

Enfin, je voudrais prévenir trois objections courantes:

  1. Certaines personnes soutiennent que l'utilisation returnajoute de la clarté, car elle signale «cette fonction renvoie une valeur». Mais comme expliqué ci-dessus, chaque fonction renvoie quelque chose dans R. Penser returnà un marqueur de retour d'une valeur n'est pas seulement redondant, il est activement trompeur .

  2. De même, le Zen of Python a une merveilleuse ligne directrice qui doit toujours être suivie:

    Explicite vaut mieux qu'implicite.

    Comment la suppression de redondance returnne 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 que returnpour ce cas: invisible. Par exemple, j'écrirais

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
  3. Mais 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 returnmarqueurs 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é.


Je devrais peut-être ajouter que certaines personnes préconisent de l'utiliser returnpour le rendre plus similaire à d'autres langues. Mais c'est un mauvais argument: les autres langages de programmation fonctionnels ont tendance à ne pas utiliser returnnon plus. Ce ne sont que les langages impératifs , où toutes les expressions n'ont pas de valeur, qui l'utilisent.
Konrad Rudolph

J'en suis venu à cette question avec l'idée que l'utilisation des returnsupports 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'utiliser returnexplicitement (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 a return. Merci pour ces réflexions et cette perspicacité !!
Kasper Thystrup Karstensen

6

Je pense returnque 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:

local({
1
2
3
})

eval(expression({
1
2
3
}))

(function() {
1
2
3
})()

Ce qui returnne 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'utilise returntrès rarement et jamais à la fin d'une fonction.

 if(a) {
   return(a)
 } else {
   return(b)
 }

... cela peut être réécrit comme if(a) a else bce qui est beaucoup mieux lisible et moins entre crochets. Pas besoin du returntout ici. Mon cas prototype d'utilisation de "retour" serait quelque chose comme ...

ugly <- function(species, x, y){
   if(length(species)>1) stop("First argument is too long.")
   if(species=="Mickey Mouse") return("You're kidding!")
   ### do some calculations 
   if(grepl("mouse", species)) {
      ## do some more calculations
      if(species=="Dormouse") return(paste0("You're sleeping until", x+y))
      ## do some more calculations
      return(paste0("You're a mouse and will be eating for ", x^y, " more minutes."))
      }
   ## some more ugly conditions
   # ...
   ### finally
   return("The end")
   }

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.

getout <- TRUE 
# if getout==TRUE then the value of EXP, LOC, and FUN will be "OUTTA HERE"
# .... if getout==FALSE then it will be `3` for all these variables    

EXP <- eval(expression({
   1
   2
   if(getout) return("OUTTA HERE")
   3
   }))

LOC <- local({
   1
   2
   if(getout) return("OUTTA HERE")
   3
   })

FUN <- (function(){
   1
   2
   if(getout) return("OUTTA HERE")
   3
   })()

identical(EXP,LOC)
identical(EXP,FUN)

Aujourd'hui, j'ai trouvé un cas où l'on peut réellement avoir besoin return(mon laid exemple ci-dessus est très artificiel): supposons que vous ayez besoin de tester si une valeur est NULLou NA: dans ces cas, retournez une chaîne vide, sinon retournez la charactervaleur. Mais un test de is.na(NULL)donne une erreur, il semble donc que cela ne peut être fait qu'avec if(is.null(x)) return(""), puis continuer if(is.na(x)) ...... (On peut utiliser à la length(x)==0place de is.null(x)mais tout de même, il n'est pas possible d'utiliser length(x)==0 | is.na(x)si xc'est le cas NULL.)
lebatsnok

1
C'est parce que vous avez utilisé |(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érez if (TRUE | stop()) print(1)versusif (TRUE || stop()) print(1)
asac

2

return peut augmenter la lisibilité du code:

foo <- function() {
    if (a) return(a)       
    b     
}

3
Peut-être que c'est possible. Mais cela ne se fait pas dans votre exemple. Au lieu de cela, il obscurcit (ou plutôt complexifie) le flux de code.
Konrad Rudolph

1
votre fonction peut être simplifiée en: 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)
lebatsnok

1

L'argument de la redondance a beaucoup été soulevé ici. À mon avis, ce n'est pas une raison suffisante pour omettrereturn() . 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'omission return()a un désavantage constant. Cela semble très spécifique à chaque cas et à l'utilisateur.

Voici donc où j'en suis.

function(){
  #do stuff
  ...
  abcd
}

Je suis mal à l'aise avec les variables "orphelines" comme dans l'exemple ci-dessus. Allait abcdfaire 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?

function(){
  #do stuff
  ...
  return(abdc)
}

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.


"Abcd allait-il faire partie d'une déclaration que je n'ai pas fini d'écrire?" - En quoi est-ce différent de toute autre expression que vous écrivez, cependant? Ceci, je pense, est au cœur de notre désaccord. Avoir un stand variable seul peut être particulier dans un langage de programmation impératif, mais c'est tout à fait normal et attendu dans un langage de programmation fonctionnel. Le problème, selon moi, est simplement que vous n'êtes pas familier avec la programmation fonctionnelle (le fait que vous parliez d '«instructions» au lieu d' «expressions» renforce cela).
Konrad Rudolph

C'est différent parce que chaque autre instruction fait généralement quelque chose de manière plus évidente: c'est une affectation, une comparaison, un appel de fonction ... Oui, mes premières étapes de codage étaient dans des langages impératifs et j'utilise toujours des langages impératifs. Avoir des repères visuels uniformes dans toutes les langues (partout où les langues le permettent) facilite mon travail. Un 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.
cymon

De plus, je ne suis ni ingénieur logiciel ni informaticien. Ne lisez pas trop de nuances dans mon utilisation de la terminologie.
cymon

Juste pour clarifier: «Redondant et inutile ne sont pas nécessairement synonymes. C’est là que nous ne sommes pas d’accord. » - Non, je suis totalement d'accord avec cela, et je l'ai explicitement souligné dans ma réponse. La redondance peut être utile ou même cruciale . Mais cela doit être montré activement, pas supposé. Je comprends votre argument pour pourquoi vous pensez que cela tient 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).
Konrad Rudolph
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.