Chiamare esplicitamente il ritorno in una funzione o meno


199

Qualche tempo fa sono stato rimproverato da Simon Urbanek dal team principale di R (credo) per aver raccomandato a un utente di chiamare esplicitamente returnalla fine di una funzione (il suo commento è stato comunque eliminato):

foo = function() {
  return(value)
}

invece ha raccomandato:

foo = function() {
  value
}

Probabilmente in una situazione come questa è necessario:

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

Il suo commento ha fatto luce sul perché non chiamare a returnmeno che non sia strettamente necessario, ma è stato eliminato.

La mia domanda è: perché non chiamare returnpiù velocemente o meglio, e quindi preferibile?


12
returnnon è necessario nemmeno nell'ultimo esempio. La rimozione returnpuò rendere un po 'più veloce, ma a mio avviso ciò è dovuto al fatto che R è un linguaggio di programmazione funzionale.
kohske,

4
@kohske Potresti espandere il tuo commento in una risposta, includendo maggiori dettagli sul perché è più veloce e su come questo è correlato al fatto che R sia un linguaggio di programmazione funzionale?
Paul Hiemstra,

2
returninduce un salto non locale e il salto esplicito non locale è insolito per FP. In realtà, ad esempio, lo schema non ha return. Penso che i miei commenti siano troppo brevi (e forse errati) come risposta.
kohske,

2
F # non ha return, break, continueo, che è a volte noioso.
Colinfang,

Risposte:


129

La domanda era: perché la chiamata (esplicitamente) non ritorna più veloce o migliore, e quindi preferibile?

Non vi è alcuna dichiarazione nella documentazione R che assuma tale presupposto.
La pagina principale? 'Funzione' dice:

function( arglist ) expr
return(value)

È più veloce senza chiamare return?

Entrambi function()e return()sono funzioni primitive e function()si ritorna all'ultimo valore valutato anche senza includere return()funzione.

Chiamare return()come .Primitive('return')con l'ultimo valore come argomento farà lo stesso lavoro ma necessita di una chiamata in più. In modo che questo (spesso) non sia necessario.Primitive('return') chiamata possa attingere risorse aggiuntive. La misurazione semplice mostra tuttavia che la differenza risultante è molto piccola e quindi non può essere la ragione per non utilizzare il ritorno esplicito. Il seguente diagramma 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 della funzione

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

È meglio senza chiamare il ritorno?

Return è un buon strumento per progettare chiaramente "foglie" di codice in cui la routine dovrebbe terminare, saltare fuori dalla funzione e restituire 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, non può usare return () in quanto non è richiesto.

I programmatori core R utilizzano entrambi gli approcci, ad es. con e senza return esplicito () come è possibile trovare in fonti di funzioni 'base'.

Molte volte viene utilizzato solo return () (nessun argomento) restituendo NULL nei casi per arrestare in modo condizionale la funzione.

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

La mia opinione è che la domanda dovrebbe essere: c'è qualche pericolo nell'utilizzare il ritorno esplicito proveniente dall'implementazione di R?

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


4
Grazie per un'ottima risposta Credo che non ci sia pericolo nell'uso returne dipende dalla preferenza del programmatore se usarlo o meno.
Paul Hiemstra,

38
La velocità di returnè davvero l'ultima cosa di cui dovresti preoccuparti.
Hadley,

2
Penso che questa sia una cattiva risposta. Le ragioni derivano da un sostanziale disaccordo sul valore dell'uso di returnchiamate di funzione non necessarie . La domanda che dovresti porre non è quella che proponi alla fine. Invece è: “perché dovrei usare un ridondante return? Quale vantaggio offre? " A quanto pare, la risposta è "non molto", o addirittura "nessuno". La tua risposta non lo apprezza.
Konrad Rudolph,

@KonradRudolph ... hai ripetuto in realtà quello che Paul stava chiedendo in origine (perché il ritorno esplicito è negativo). E vorrei sapere anche la risposta giusta (quella giusta per chiunque) :). Consideri di fornire la tua spiegazione per gli utenti di questo sito?
Petr Matousu,

1
@Dason Ho collegato altrove un post di Reddit che spiega perché questo argomento è imperfetto in questo contesto. Sfortunatamente il commento sembra essere rimosso automaticamente ogni volta. La versione breve returnè come un commento esplicito che dice "incrementa x di 1", accanto a un pezzo di codice che fa x = x + 2. In altre parole, la sua esplicitazione è (a) assolutamente irrilevante e (b) trasmette informazioni errate . Perché returnla semantica in R è, puramente, "interrompe questa funzione". Essa non significa lo stesso che returnin altre lingue.
Konrad Rudolph,

103

Se tutti sono d'accordo

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

dovremmo smettere tutti di usare return alla fine di una funzione? Certamente non lo farò e vorrei spiegare il perché. Spero di sapere se altre persone condivideranno 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 returnè che, come ha sottolineato Paul, ci sono altri posti nel corpo di una funzione in cui potresti averne bisogno. E se sei costretto a utilizzare returnda qualche parte nel mezzo della tua funzione, perché non fare tuttoreturn esplicite dichiarazioni? Odio essere incoerente. Inoltre penso che il codice legga meglio; si può scansionare la funzione e vedere facilmente tutti i punti e i valori di uscita.

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 è persino conforme ad alcuni standard di programmazione che promuovono 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)
}

Sarebbe molto più difficile riscrivere usando una singola istruzione return: avrebbe bisogno di più se breakun intricato sistema di variabili booleane per propagarle. Tutto questo per dire che la singola regola di ritorno non gioca bene con R. Quindi, se hai bisogno di usare returnin alcuni punti del corpo della tua funzione, perché non essere coerente e usarlo ovunque?

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


7
+1, c'è una chiara necessità per l' returnaffermazione in alcuni casi, come ha mostrato @flodel. In alternativa, ci sono situazioni in cui è meglio omettere un'istruzione return, ad esempio molte e molte piccole chiamate di funzione. In tutti gli altri, diciamo il 95%, dei casi non importa se si usa returno no, e si riduce alle preferenze. Mi piace usare return in quanto è più esplicito in ciò che vuoi dire, quindi più leggibile. Forse questa discussione è simile al <-vs =?
Paul Hiemstra,

7
Questo sta trattando R come un linguaggio di programmazione imperativo, che non lo è: è un linguaggio di programmazione funzionale. La programmazione funzionale funziona semplicemente in modo diverso e l'utilizzo returnper restituire un valore è privo di senso, alla pari con la scrittura if (x == TRUE)anziché if (x).
Konrad Rudolph,

4
Si riscrive anche foocome foo <- function(x) if (a) a else b(con interruzioni di riga, se necessario). Non è necessario un ritorno esplicito o un valore intermedio.
Hadley,

26

Questa è una discussione interessante Penso che l'esempio di @ flodel sia eccellente. Tuttavia, penso che illustri il mio punto (e @koshke lo menziona in un commento) che returnha senso quando si utilizza uno stile di codifica imperativo anziché funzionale .

Per non ribadire il punto, ma avrei riscritto in fooquesto modo:

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

Uno stile funzionale evita i cambiamenti di stato, come la memorizzazione del valore di output. In questo stile, returnè fuori posto; foosembra più una funzione matematica.

Sono d'accordo con @flodel: l'uso di un intricato sistema di variabili booleane barsarebbe meno chiaro e inutile quando lo hai fatto return. Ciò che rende barcosì suscettibile alle returndichiarazioni è che è scritto in uno stile imperativo. In effetti, le variabili booleane rappresentano i cambiamenti di "stato" evitati in uno stile funzionale.

È davvero difficile riscrivere barin stile funzionale, perché è solo uno 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 whileciclo sarebbe il più difficile da riscrivere, perché è controllato dalle modifiche di stato a a.

La perdita di velocità causata da una chiamata returnè trascurabile, ma l'efficienza ottenuta evitando returne riscrivendo in uno stile funzionale è spesso enorme. Dire ai nuovi utenti di smettere di usare returnprobabilmente non sarà d'aiuto, ma guidarli verso uno stile funzionale ne trarrà vantaggio.


@Paul returnè necessario in stile imperativo perché spesso vuoi uscire dalla funzione in diversi punti in un ciclo. Uno stile funzionale non utilizza i loop e quindi non è necessario return. In uno stile puramente funzionale, la chiamata finale è quasi sempre il valore di ritorno desiderato.

In Python, le funzioni richiedono a return un'istruzione. Tuttavia, se hai programmato la tua funzione in uno stile funzionale, probabilmente ne avrai solo unareturn frase: alla fine della tua funzione.

Usando un esempio da un altro post StackOverflow, supponiamo di volere una funzione che restituisca TRUEse tutti i valori in un datox avessero 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 cade naturalmente alle estremità della funzione. Ancora una volta, sembra più una funzione matematica.

@Vedi Gli avvertimenti delineati ?ifelsesono decisamente interessanti, ma non credo che stiano cercando di dissuadere l'uso della funzione. In effetti, ifelseha il vantaggio di vettorializzare automaticamente le funzioni. 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 è valida quando length(a)è 1. Ma se si riscrive foocon unifelse

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

Ora foofunziona su qualsiasi lunghezza di a. In effetti, funzionerebbe anche quando aè una matrice. Restituire un valore della stessa forma di testuna funzione che aiuta con la vettorializzazione, non è un problema.


Non mi è chiaro il motivo per cui returnnon si adatta a uno stile funzionale di programmazione. Se uno sta programmando in modo imperativo o funzionale, ad un certo punto una funzione o subroutine deve restituire qualcosa. Ad esempio, la programmazione funzionale in Python richiede ancora returnun'istruzione. Potresti approfondire di più su questo punto.
Paul Hiemstra,

In questa situazione, usare ifelse(a,a,b)è una mia piccola pipì. Sembra che ogni battuta ?ifelsesia urlante, "non usare me invece di if (a) {a} else b". es. "... restituisce un valore con la stessa forma di test", "se yeso nosono troppo corti, i loro elementi vengono riciclati.", "la modalità del risultato può dipendere dal valore di test", "l'attributo di classe del risultato è preso da teste potrebbe essere inappropriato per i valori selezionati da yese no"
GSee

A seconda vista, foonon ha molto senso; restituirà sempre VERO o b. Usandolo ifelseverrà restituito 1 o più TRUE e / o 1 o più bs. Inizialmente, ho pensato che l'intento della funzione fosse quello di dire "se qualche affermazione è VERA, restituire qualcosa, altrimenti, restituire qualcos'altro". Non penso che dovrebbe essere vettorializzato, perché poi diventerebbe "restituisce gli elementi di un oggetto VERO, e per tutti gli elementi che non sono VERO, ritorna b.
GSee

22

Sembra che senza di return()essa sia 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__ _ __ _ __ _ __ _ __ _ ___

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


Potresti commentare il motivo per cui si verifica questa differenza?
Paul Hiemstra,

4
@PaulHiemstra La risposta di Petr copre uno dei motivi principali per questo; due chiamate durante l'utilizzo return(), una in caso contrario. È totalmente ridondante alla fine di una funzione poiché function()restituisce il suo ultimo valore. Lo noterai solo in molte ripetizioni di una funzione in cui non viene fatto molto all'interno, in modo che il costo di return()diventi parte del tempo di calcolo totale della funzione.
Gavin Simpson,

13

Un problema con il non mettere esplicitamente 'return' alla fine è che se si aggiungono ulteriori istruzioni alla fine del metodo, improvvisamente il valore di ritorno è sbagliato:

foo <- function() {
    dosomething()
}

Questo restituisce il valore di dosomething().

Ora arriviamo il giorno successivo e aggiungiamo una nuova linea:

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

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

1
sì, in realtà trovo che un ritorno esplicito () sia utile durante il debug; una volta ripulito il codice, il suo bisogno è meno convincente e preferisco l'eleganza di non averlo ...
Patrick,

Ma questo non è in realtà un problema nel codice reale, è puramente teorico. Il codice che potrebbe soffrire di questo ha un problema molto più grande: un flusso di codice oscuro e fragile che è così non ovvio che semplici aggiunte lo rompono.
Konrad Rudolph,

@KonradRudolph Penso che tu stia facendo un No-True Scotsman su di esso ;-) "Se questo è un problema nel tuo codice, sei un cattivo programmatore!". Non sono davvero d'accordo. Penso che mentre puoi cavartela con scorciatoie su piccoli pezzi di codice, dove conosci ogni riga a memoria, questo tornerà a morderti man mano che il tuo codice diventa più grande.
Hugh Perkins,

2
@HughPerkins Non è un vero scozzese ; piuttosto, è un'osservazione empirica sulla complessità del codice, supportata da decenni di buone pratiche di ingegneria del software: mantenere brevi le singole funzioni e rendere evidente il flusso di codice. E omettere returnnon è una scorciatoia, è lo stile corretto nella programmazione funzionale. L'uso di returnchiamate di funzione non necessarie è un'istanza della programmazione di culto del carico .
Konrad Rudolph,

Beh ... non vedo come questo ti impedisce di aggiungere qualcosa dopo la tua returnaffermazione e di non notare che non verrà eseguita. Potresti anche aggiungere un commento dopo il valore che vuoi restituire, ad es.dosomething() # this is my return value, don't add anything after it unless you know goddam well what you are doing
lebatsnok,

10

La mia domanda è: perché non chiama returnpiù velocemente

È più veloce perché returnè una funzione (primitiva) in R, il che significa che l'utilizzo nel codice comporta il costo di una chiamata di funzione. Confronta questo con la maggior parte degli altri linguaggi di programmazione, dove returnè una parola chiave, ma non una chiamata di funzione: non si traduce in alcuna esecuzione del codice di runtime.

Detto questo, chiamare una funzione primitiva in questo modo è abbastanza veloce in R, e chiamare returncomporta un sovraccarico minuscolo. Questo non è l'argomento per omettere return.

o meglio, e quindi preferibile?

Perché non c'è motivo di usarlo.

Perché è ridondante e non aggiunge ridondanza utile .

Per essere chiari: la ridondanza a volte può essere utile . Ma la maggior parte della ridondanza non è di questo tipo. Invece, è del tipo che aggiunge disordine visuale senza aggiungere informazioni: è l'equivalente di programmazione di una parola di riempimento o di un chartjunk ).

Considera l'esempio seguente di un commento esplicativo, che è universalmente riconosciuto come cattiva ridondanza perché il commento parafrasa semplicemente ciò che il codice già esprime:

# Add one to the result
result = x + 1

L'uso returnin R rientra nella stessa categoria, poiché R è un linguaggio di programmazione funzionale e in R ogni chiamata di funzione ha un valore . Questa è una proprietà fondamentale di R. E una volta che vedi il codice R dalla prospettiva che ogni espressione (inclusa ogni chiamata di funzione) ha un valore, la domanda diventa: "perché dovrei usare return?" Deve esserci un motivo positivo, poiché il valore predefinito non è usarlo.

Uno di questi motivi positivi è segnalare l'uscita anticipata da una funzione, ad esempio in una clausola di guardia :

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

Questo è un uso valido e non ridondante di return. Tuttavia, tali clausole di guardia sono rare in R rispetto ad altre lingue e poiché ogni espressione ha un valore, un regolare ifnon richiede return:

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

Possiamo anche riscrivere in fquesto modo:

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

... dove if (cond) exprè uguale a if (cond) expr else NULL.

Infine, vorrei prevenire tre obiezioni comuni:

  1. Alcune persone sostengono che l'uso returnaggiunge chiarezza, perché segnala "questa funzione restituisce un valore". Ma come spiegato sopra, ogni funzione restituisce qualcosa in R. Pensare returncome indicatore di restituzione di un valore non è solo ridondante, è attivamente fuorviante .

  2. Allo stesso modo, lo Zen di Python ha una meravigliosa linea guida che dovrebbe sempre essere seguita:

    Esplicito è meglio che implicito.

    In che modo l'abbandono ridondante returnnon viola questo? Perché il valore restituito di una funzione in un linguaggio funzionale è sempre esplicito: è la sua ultima espressione. Questo è di nuovo lo stesso argomento su explicitness vs ridondanza.

    In effetti, se si desidera esplicitare, utilizzarlo per evidenziare l'eccezione alla regola: contrassegnare le funzioni che non restituiscono un valore significativo, che vengono chiamate solo per i loro effetti collaterali (come cat). Tranne R ha un indicatore migliore rispetto returnper questo caso: invisible. Ad esempio, scriverei

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
  3. Ma per quanto riguarda le funzioni lunghe? Non sarà facile perdere la traccia di ciò che viene restituito?

    Due risposte: prima, non proprio. La regola è chiara: il ultima espressione di una funzione è il suo valore. Non c'è nulla di cui tenere traccia.

    Ma soprattutto, il problema nelle funzioni lunghe non è la mancanza di returnmarcatori espliciti . È la lunghezza della funzione . Le funzioni lunghe quasi (?) Violano sempre il principio della singola responsabilità e anche quando non lo fanno trarranno vantaggio dalla separazione per leggibilità.


Forse dovrei aggiungere che alcune persone sostengono l'uso returnper renderlo più simile ad altre lingue. Ma questo è un cattivo argomento: altri linguaggi di programmazione funzionale tendono a non usare returnneanche. Sono solo le lingue imperative , dove non tutte le espressioni hanno un valore, che la usano.
Konrad Rudolph,

Sono arrivato a questa domanda con l'opinione che l'utilizzo dei returnsupporti è esplicitamente migliore e ho letto la tua risposta con piena critica. La tua risposta mi ha portato a riflettere su quella visione. Penso che la necessità di usare returnesplicitamente (almeno nel mio caso), sia legata alla necessità di poter meglio rivedere le mie funzioni in un momento successivo. Con l'idea che le mie funzioni semplicemente potrebbero essere troppo complesse, ora posso vedere che un obiettivo per migliorare il mio stile di programmazione, sarebbe quello di cercare di strutturare i codici per mantenere la chiarezza senza a return. Grazie per quelle riflessioni e intuizioni !!
Kasper Thystrup Karstensen il

6

Penso returna un trucco. Come regola generale, il valore dell'ultima espressione valutata in una funzione diventa il valore della funzione - e questo schema generale si trova in molti punti. Tutti i seguenti elementi valutano 3:

local({
1
2
3
})

eval(expression({
1
2
3
}))

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

Quello returnche fa non è in realtà restituire un valore (questo viene fatto con o senza di esso) ma "interrompere" la funzione in modo irregolare. In questo senso, è l'equivalente più vicino dell'istruzione GOTO in R (ci sono anche break e next). Uso returnmolto raramente e mai alla fine di una funzione.

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

... questo può essere riscritto in quanto if(a) a else bmolto più leggibile e meno riccio. Non c'è bisogno di returnaffatto qui. Il mio caso prototipo di "ritorno" sarebbe qualcosa di simile ...

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

In generale, la necessità di molti ritorni suggerisce che il problema è brutto o mal strutturato.g

<>

return non ha davvero bisogno di una funzione per funzionare: puoi usarla per uscire da una serie 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)

Oggi ho trovato un caso in cui uno potrebbe effettivamente essere necessario return(il mio brutto esempio sopra è altamente artificiale): supponiamo che tu debba verificare se un valore è NULLo NA: in questi casi, restituire una stringa vuota, altrimenti restituire il charactervalore. Ma un test di is.na(NULL)dà un errore, quindi sembra che possa essere fatto solo con if(is.null(x)) return("")e poi continuando con if(is.na(x)) ...... (Si può usare length(x)==0invece di, is.null(x)ma comunque, non è possibile usare length(x)==0 | is.na(x)se lo xè NULL.)
lebatsnok,

1
Questo perché hai usato |(OR vettorializzato in cui vengono valutati entrambi i lati) anziché ||(OR di cortocircuito, non vettorializzato, dove i predicati vengono valutati a turno). Considerare if (TRUE | stop()) print(1)controif (TRUE || stop()) print(1)
asac

2

return può aumentare la leggibilità del codice:

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

3
Forse può, a quello. Ma non lo fa nel tuo esempio. Invece oscura (o meglio, complessa) il flusso del codice.
Konrad Rudolph,

1
la tua funzione può essere semplificata per: foo <- function() a || b(che è più leggibile IMO; in ogni caso, non c'è leggibilità "pura" ma leggibilità secondo l'opinione di qualcuno: ci sono persone che dicono che il linguaggio assembly è perfettamente leggibile)
lebatsnok

1

L'argomento della ridondanza è emerso molto qui. Secondo me non è una ragione sufficiente per omettere return(). La ridondanza non è automaticamente una cosa negativa. Se utilizzato in modo strategico, la ridondanza rende il codice più chiaro e più manutenibile.

Considera questo esempio: i parametri di funzione hanno spesso valori predefiniti. Pertanto, la specifica di un valore uguale a quello predefinito è ridondante. Solo che rende evidente il comportamento che mi aspetto. Non c'è bisogno di richiamare la manpage di funzione per ricordarmi quali sono i valori predefiniti. E non preoccuparti che una versione futura della funzione cambi i suoi valori predefiniti.

Con una penalità di prestazione trascurabile per la chiamata return()(secondo i benchmark pubblicati qui da altri) si riduce allo stile piuttosto che giusto e sbagliato. Perché qualcosa sia "sbagliato", ci deve essere un chiaro svantaggio, e nessuno qui ha dimostrato in modo soddisfacente che l'inclusione o l'omissione return()ha uno svantaggio consistente. Sembra molto specifico del caso e specifico dell'utente.

Quindi qui è dove mi trovo su questo.

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

Mi sento a disagio con le variabili "orfane" come nell'esempio sopra. Farà abcdparte di una dichiarazione che non ho finito di scrivere? È un residuo di una giunzione / modifica nel mio codice e deve essere eliminato? Ho accidentalmente incollato / spostato qualcosa da qualche altra parte?

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

Al contrario, questo secondo esempio mi rende ovvio che si tratta di un valore di ritorno previsto, piuttosto che di un codice incidente o incompleto. Per me questa ridondanza non è assolutamente inutile.

Naturalmente, una volta che la funzione è terminata e funzionante, potrei rimuovere il ritorno. Ma rimuoverlo è di per sé un ulteriore passaggio ridondante e, a mio avviso, più inutile che includerereturn() in primo luogo.

Detto questo, non uso return()in breve funzioni senza linea singola senza nome. Lì costituisce una grande frazione del codice della funzione e quindi causa principalmente disordine visivo che rende il codice meno leggibile. Ma per funzioni più grandi definite formalmente e nominate, lo uso e probabilmente continuerò a farlo.


"Abcd avrebbe fatto parte di una dichiarazione che non avevo finito di scrivere?" - In che modo differisce da qualsiasi altra espressione che scrivi, però? Questo, penso sia il nocciolo del nostro disaccordo. Avere una posizione variabile da sola potrebbe essere peculiare in un linguaggio di programmazione imperativo, ma è del tutto normale e previsto in un linguaggio di programmazione funzionale. Il problema, sostengo, è semplicemente che non hai familiarità con la programmazione funzionale (il fatto che tu parli di "dichiarazioni" invece di "espressioni" lo rafforza).
Konrad Rudolph,

È diverso perché ogni altra affermazione di solito fa qualcosa in un modo più ovvio: è un compito, un confronto, una chiamata di funzione ... Sì, i miei primi passi di programmazione erano in linguaggi imperativi e continuo a usare linguaggi imperativi. Avere segnali visivi uniformi tra le lingue (ovunque le lingue lo consentano) mi facilita il lavoro. A return()in R non costa nulla. È oggettivamente ridondante, ma essere "inutile" è il tuo giudizio soggettivo. Ridondanti e inutili non sono necessariamente sinonimi. È qui che non siamo d'accordo.
cymon il

Inoltre, non sono un ingegnere informatico né un informatico. Non leggere troppe sfumature nel mio uso della terminologia.
cymon il

Giusto per chiarire: “Ridondanti e inutili non sono necessariamente sinonimi. È qui che non siamo d'accordo. " - No, sono totalmente d'accordo con questo, e ho esplicitamente sottolineato questo punto nella mia risposta. La ridondanza può essere utile o addirittura cruciale . Ma questo deve essere mostrato attivamente, non assunto. Capisco la tua argomentazione sul perché pensi che ciò valga return, e anche se non sono convinto penso che sia potenzialmente valido (è sicuramente in un linguaggio imperativo ... la mia convinzione è che non si traduca in linguaggi funzionali).
Konrad Rudolph,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.