Chiamata esplicita di ritorno in una funzione oppure no

Qualche tempo fa sono stato rimproverato da Simon Urbanek del team R (credo) per raccomandare a un utente di chiamare esplicitamente il return alla fine di una funzione (il suo commento è stato cancellato):

 foo = function() { return(value) } 

invece ha raccomandato:

 foo = function() { value } 

Probabilmente in una situazione come questa è richiesto:

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

Il suo commento chiarisce perché non chiamare il return meno che non sia strettamente necessario, ma questo è stato cancellato.

La mia domanda è: perché non chiamare il return più veloce o migliore, e quindi preferibile?

La domanda era: perché non è (esplicitamente) chiamare il ritorno più veloce o migliore, e quindi preferibile?

Non c’è statema nella documentazione di R che faccia una tale ipotesi.
La pagina man? “Function” dice:

 function( arglist ) expr return(value) 

È più veloce senza chiamare il ritorno?

Sia function() che return() sono funzioni primitive e la function() stessa restituisce l’ultimo valore valutato anche senza includere la funzione return() .

Chiamare return() come .Primitive('return') con quell’ultimo valore come argomento farà lo stesso lavoro ma richiederà una chiamata in più. In modo che questa chiamata (spesso) non necessaria .Primitive('return') possa attingere risorse aggiuntive. La semplice misurazione tuttavia mostra che la differenza risultante è molto piccola e quindi non può essere la ragione per non utilizzare il ritorno esplicito. Il seguente grafico viene creato dai dati selezionati in questo modo:

 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 

Confronto del tempo trascorso funzione

L'immagine qui sopra potrebbe leggermente differire sulla tua piattaforma. Sulla base dei dati misurati, la dimensione dell'object restituito non sta causando alcuna differenza, il numero di ripetizioni (anche se ridimensionato) rende solo una piccola differenza, che in realtà con dati reali e algoritmo reale non può essere conteggiata o rendere il vostro lo script funziona più velocemente.

È meglio senza chiamare il ritorno?

Return è un buon strumento per la progettazione chiara di "foglie" di codice in cui la routine dovrebbe terminare, saltare fuori dalla funzione e restituire il valore.

 # 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 > 

Dipende dalla strategia e dallo stile di programmazione del programmatore quale stile usa, può usare no return () in quanto non è richiesto.

I programmatori principali R utilizzano entrambi gli approcci, ad es. con e senza ritorno esplicito () come è ansible trovare nelle fonti di funzioni 'base'.

Molte volte solo return () viene utilizzato (nessun argomento) restituendo NULL in caso di arresto condizionale della funzione.

Non è chiaro se sia migliore o meno come utente standard o analista che usa R non può vedere la vera differenza.

La mia opinione è che la domanda dovrebbe essere: C'è qualche pericolo nell'usare un ritorno esplicito proveniente dall'implementazione R?

O, forse meglio, il codice funzione di scrittura utente dovrebbe sempre chiedere: quale è l'effetto nel non utilizzare il ritorno esplicito (o il posizionamento dell'object da restituire come ultima foglia del ramo di codice) nel codice funzione?

Se tutti sono d’accordo

  1. return non è necessario alla fine del corpo di una funzione
  2. non usare il return è leggermente più veloce (secondo il test di @ Alan, 4,3 microsecondi contro 5,1)

dovremmo smettere di usare return alla fine di una funzione? Di certo non lo farò, e mi piacerebbe spiegarne il motivo. Spero di sentire se altre persone condividono la mia opinione. E mi scuso se non è una risposta diretta all’OP, ma più come un lungo commento soggettivo.

Il mio problema principale con il non utilizzo del return è che, come ha sottolineato Paul, ci sono altri posti nel corpo di una funzione dove potrebbe essere necessario. E se sei costretto a usare return da qualche parte nel bel mezzo della tua funzione, perché non rendere esplicite tutte le dichiarazioni di return ? Odio essere incoerente. Inoltre penso che il codice sia migliore; si può scansionare la funzione e vedere facilmente tutti i punti di uscita e i valori.

Paolo ha usato questo esempio:

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

Sfortunatamente, si potrebbe sottolineare che può essere facilmente riscritto come:

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

Quest'ultima versione è conforms anche ad alcuni standard di programmazione che sostengono una dichiarazione di ritorno per funzione. Penso che un esempio migliore avrebbe potuto essere:

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

Questo sarebbe molto più difficile da riscrivere usando una singola dichiarazione di ritorno: avrebbe bisogno di più break e un complesso sistema di variabili booleane per propagarle. Tutto questo per dire che la regola del ritorno singolo non gioca bene con R. Quindi, se hai bisogno di usare il return in alcuni punti del corpo della tua funzione, perché non essere coerente e usarlo ovunque?

Non penso che l'argomento della velocità sia valido. Una differenza di 0,8 microsecondi non è nulla quando inizi a guardare le funzioni che effettivamente fanno qualcosa. L'ultima cosa che posso vedere è che sta scrivendo meno ma hey, non sono pigro.

Sembra che senza return() è più veloce …

 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 

____ EDIT __ _ __ _ __ _ __ _ __ _ ___

benchmark(fuu(x),foo(x),replications=1e7) ad altri benchmark ( benchmark(fuu(x),foo(x),replications=1e7) ) e il risultato è invertito ... Proverò su un server.

Questa è una discussione interessante. Penso che l’esempio di @ flodel sia eccellente. Tuttavia, penso che illustri il mio punto (e @koshke accenna a questo in un commento) che il return ha senso quando si usa un imperativo invece di uno stile di codifica funzionale .

Non per credere al punto, ma avrei riscritto foo questo modo:

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

Uno stile funzionale evita cambiamenti di stato, come la memorizzazione del valore output . In questo stile, il return è fuori luogo; foo sembra più una funzione matematica.

Sono d’accordo con @flodel: usare un intricato sistema di variabili booleane nella bar sarebbe meno chiaro, e inutile quando si return . Ciò che rende il bar così piacevole da return dichiarazioni è che è scritto in uno stile imperativo. In effetti, le variabili booleane rappresentano i cambiamenti di “stato” evitati in uno stile funzionale.

È davvero difficile riscrivere la bar in stile funzionale, perché è solo pseudocodice, ma l’idea è qualcosa del genere:

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

Il ciclo while sarebbe il più difficile da riscrivere, poiché è controllato dalle modifiche di stato a a .

La perdita di velocità causata da una chiamata al return è trascurabile, ma l'efficienza ottenuta evitando il return e la riscrittura in uno stile funzionale è spesso enorme. Dire ai nuovi utenti di smettere di usare return probabilmente non aiuterà, ma orientarli verso uno stile funzionale darà i suoi frutti.


Il return @Paul è necessario in stile imperativo perché spesso si desidera uscire dalla funzione in punti diversi di un ciclo. Uno stile funzionale non utilizza cicli e pertanto non ha bisogno di return . In uno stile puramente funzionale, la chiamata finale è quasi sempre il valore di ritorno desiderato.

In Python, le funzioni richiedono una dichiarazione di return . Tuttavia, se hai programmato la tua funzione in uno stile funzionale, probabilmente avrai una sola dichiarazione di return : alla fine della tua funzione.

Usando un esempio da un altro post di StackOverflow, diciamo che volevamo una funzione che restituisse TRUE se tutti i valori in una data x avevano una lunghezza dispari. Potremmo usare due stili:

 # 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) 

In uno stile funzionale, il valore da restituire ricade naturalmente ai fini della funzione. Di nuovo, sembra più una funzione matematica.

@GSee Gli avvertimenti delineati in ?ifelse sono decisamente interessanti, ma non penso che stiano cercando di dissuadere l'uso della funzione. Infatti, ifelse ha il vantaggio di funzioni di vettorizzazione automatica. Ad esempio, considera una versione leggermente modificata di foo :

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

Questa funzione funziona bene quando la length(a) è 1. Ma se hai riscritto foo con un ifelse

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

Ora foo funziona su qualsiasi lunghezza di a . In effetti, funzionerebbe anche quando a è una matrice. Restituire un valore con la stessa forma del test è una caratteristica che aiuta con la vettorizzazione, non un problema.

Un problema nel non mettere “return” esplicitamente alla fine è che se si aggiungono ulteriori istruzioni alla fine del metodo, improvvisamente il valore restituito è sbagliato:

 foo <- function() { dosomething() } 

Questo restituisce il valore di dosomething() .

Ora arriviamo il giorno dopo e aggiungiamo una nuova riga:

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

Volevamo che il nostro codice restituisse il valore di dosomething() , ma invece non lo fa più.

Con un ritorno esplicito, questo diventa davvero ovvio:

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

Possiamo vedere che c'è qualcosa di strano in questo codice e sistemarlo:

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

Penso al return come a un trucco. Come regola generale, il valore dell’ultima espressione valutata in una funzione diventa il valore della funzione – e questo modello generale si trova in molti punti. Tutti i seguenti valori valutano a 3:

 local({ 1 2 3 }) eval(expression({ 1 2 3 })) (function() { 1 2 3 })() 

Ciò che return non è in realtà restituire un valore (questo è fatto con o senza di esso) ma “scoppiare” della funzione in modo irregolare. In questo senso, è l’equivalente più vicino dell’istruzione GOTO in R (ci sono anche break e next). Uso il return molto raramente e mai alla fine di una funzione.

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

… questo può essere riscritto come if(a) a else b che sia molto più leggibile e meno arricciato. Non c’è bisogno di return qui. Il mio caso prototipo di “ritorno” sarebbe qualcosa come …

 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") } 

Generalmente, la necessità di molti return suggerisce che il problema sia brutto o mal strutturato.g

<>

return non ha realmente bisogno di una funzione per funzionare: puoi usarlo per uscire da un insieme di espressioni da valutare.

 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) 

return può aumentare la leggibilità del codice:

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