Llamando explícitamente return en una función o no

199

Hace un tiempo, Simon Urbanek me reprendió del equipo principal de R (creo) por recomendar a un usuario que llamara explícitamente returnal final de una función (aunque su comentario fue eliminado):

foo = function() {
  return(value)
}

en cambio él recomendó:

foo = function() {
  value
}

Probablemente en una situación como esta se requiere:

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

Su comentario arrojó algo de luz sobre por qué no llamar a returnmenos que sea ​​estrictamente necesario es algo bueno, pero esto fue eliminado.

Mi pregunta es: ¿por qué no llamar returnmás rápido o mejor, y por lo tanto es preferible?

Paul Hiemstra
fuente
12
returnes innecesario incluso en el último ejemplo. La eliminación returnpuede hacer que sea un poco más rápido, pero en mi opinión esto se debe a que se dice que R es un lenguaje de programación funcional.
kohske
44
@kohske ¿Podría ampliar su comentario en una respuesta, que incluya más detalles sobre por qué es más rápido y cómo se relaciona esto con que R sea un lenguaje de programación funcional?
Paul Hiemstra
2
returninduce un salto no local, y el salto no local explícito es inusual para FP. En realidad, por ejemplo, el esquema no tiene return. Creo que mis comentarios son demasiado cortos (y tal vez incorrectos) como respuesta.
kohske
2
F # no tiene return, break, continueo, lo cual es a veces tediosas.
colinfang

Respuestas:

129

La pregunta era: ¿por qué no (explícitamente) la devolución de llamadas es más rápida o mejor, y por lo tanto preferible?

No hay ninguna declaración en la documentación de R que haga tal suposición.
La página principal? 'Función' dice:

function( arglist ) expr
return(value)

¿Es más rápido sin llamar a return?

Tanto function()y return()son funciones primitivas y el function()mismo vuelve último valor evaluado aun sin incluir return()la función.

Llamar return()como .Primitive('return')con ese último valor como argumento hará el mismo trabajo pero necesita una llamada más. Para que esta .Primitive('return')llamada (a menudo) innecesaria pueda atraer recursos adicionales. Sin embargo, la medición simple muestra que la diferencia resultante es muy pequeña y, por lo tanto, no puede ser la razón para no usar el retorno explícito. El siguiente gráfico se crea a partir de los datos seleccionados de esta manera:

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

Función de comparación del tiempo transcurrido

La imagen de arriba puede diferir ligeramente en su plataforma. Según los datos medidos, el tamaño del objeto devuelto no causa ninguna diferencia, el número de repeticiones (incluso si se amplía) hace una diferencia muy pequeña, que en palabras reales con datos reales y algoritmos reales no podría contarse o hacer que su El script se ejecuta más rápido.

¿Es mejor sin volver a llamar?

Return es una buena herramienta para diseñar claramente "hojas" de código donde la rutina debe terminar, saltar de la función y devolver el valor.

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

Depende de la estrategia y el estilo de programación del programador, qué estilo usa, no puede usar return () ya que no es necesario.

Los programadores de R core usan ambos enfoques, es decir. con y sin return explícito () como es posible encontrar en fuentes de funciones 'base'.

Muchas veces solo se usa return () (sin argumento) devolviendo NULL en casos para detener la función de manera conditial.

No está claro si es mejor o no, ya que el usuario estándar o el analista que usa R no puede ver la diferencia real.

Mi opinión es que la pregunta debería ser: ¿Existe algún peligro al usar el retorno explícito proveniente de la implementación de R?

O, tal vez mejor, el código de función de escritura del usuario siempre debe preguntar: ¿Cuál es el efecto de no usar el retorno explícito (o colocar el objeto que se devolverá como la última hoja de la rama de código) en el código de función?

Petr Matousu
fuente
44
Gracias por una muy buena respuesta. Creo que no hay peligro al usarlo return, y todo depende de la preferencia del programador de usarlo o no.
Paul Hiemstra
38
La velocidad returnes realmente lo último de lo que debería preocuparse.
Hadley
2
Creo que esta es una mala respuesta. Las razones se reducen a un desacuerdo fundamental sobre el valor del uso de returnllamadas a funciones innecesarias . La pregunta que debe hacer no es la que propone al final. En cambio, es: "¿por qué debería usar un redundante return? ¿Qué beneficio proporciona? Como resultado, la respuesta es "no mucho", o incluso "ninguno". Su respuesta no puede apreciar esto.
Konrad Rudolph el
@KonradRudolph ... repitió en realidad lo que Paul estaba preguntando originalmente (por qué el retorno explícito es malo). Y me gustaría saber la respuesta correcta (la adecuada para cualquier persona) también :). ¿Considera proporcionar su explicación para los usuarios de este sitio?
Petr Matousu
1
@Dason En otro lugar he vinculado una publicación de Reddit que explica por qué este argumento es defectuoso en este contexto. Lamentablemente, el comentario parece eliminarse automáticamente cada vez. La versión corta es que returnes como un comentario explícito que dice "incrementar x en 1", al lado de un fragmento de código x = x + 2. En otras palabras, su carácter explícito es (a) completamente irrelevante, y (b) transmite la información incorrecta . Debido a que returnla semántica en R es, simplemente, "abortar esta función". No , no significa lo mismo que returnen otros idiomas.
Konrad Rudolph
103

Si todos están de acuerdo en que

  1. return no es necesario al final del cuerpo de una función
  2. no usar returnes marginalmente más rápido (según la prueba de @ Alan, 4.3 microsegundos versus 5.1)

¿deberíamos dejar de usar returnal final de una función? Ciertamente no lo haré, y me gustaría explicar por qué. Espero saber si otras personas comparten mi opinión. Y me disculpo si no es una respuesta directa al OP, sino más bien como un largo comentario subjetivo.

Mi principal problema con el no uso returnes que, como señaló Paul, hay otros lugares en el cuerpo de una función donde puede necesitarlo. Y si se ve obligado a usar returnen algún lugar en el medio de su función, ¿por qué no hacer returnexplícitas todas las declaraciones? Odio ser inconsistente. También creo que el código se lee mejor; se puede escanear la función y ver fácilmente todos los puntos y valores de salida.

Pablo usó este ejemplo:

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

Desafortunadamente, uno podría señalar que puede reescribirse fácilmente como:

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

La última versión incluso cumple con algunos estándares de codificación de programación que recomiendan una declaración de retorno por función. Creo que un mejor ejemplo podría haber sido:

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

Esto sería mucho más difícil de reescribir usando una sola declaración de retorno: necesitaría múltiples breaksy un intrincado sistema de variables booleanas para propagarlos. Todo esto para decir que la regla de retorno único no juega bien con R. Entonces, si va a necesitar usar returnen algunos lugares del cuerpo de su función, ¿por qué no ser coherente y usarlo en todas partes?

No creo que el argumento de la velocidad sea válido. Una diferencia de 0,8 microsegundos no es nada cuando comienzas a buscar funciones que realmente hacen algo. Lo último que puedo ver es que escribe menos, pero bueno, no soy flojo.

flodel
fuente
77
+1, existe una clara necesidad de la returndeclaración en algunos casos, como mostró @flodel. Alternativamente, hay situaciones en las que es mejor omitir una declaración de devolución, por ejemplo, muchas y muchas llamadas a funciones pequeñas. En todos los demás, digamos el 95% de los casos, realmente no importa si uno usa returno no, y todo se reduce a la preferencia. Me gusta usar return, ya que es más explícito en lo que quieres decir, por lo tanto, más legible. Tal vez esta discusión es similar a <-vs =?
Paul Hiemstra
77
Esto trata a R como un lenguaje de programación imperativo, que no lo es: es un lenguaje de programación funcional. La programación funcional simplemente funciona de manera diferente, y usar returnpara devolver un valor no tiene sentido, a la par con la escritura en if (x == TRUE)lugar de if (x).
Konrad Rudolph
44
También reescribe foocomo foo <- function(x) if (a) a else b(con saltos de línea según sea necesario). No hay necesidad de retorno explícito o valor intermedio.
Hadley
26

Esta es una discusión interesante. Creo que el ejemplo de @ flodel es excelente. Sin embargo, creo que ilustra mi punto (y @koshke lo menciona en un comentario) que returntiene sentido cuando se usa un estilo de codificación imperativo en lugar de uno funcional .

No para diferir el punto, pero habría reescrito fooasí:

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

Un estilo funcional evita cambios de estado, como almacenar el valor de output. En este estilo, returnestá fuera de lugar; foose parece más a una función matemática.

Estoy de acuerdo con @flodel: usar un intrincado sistema de variables booleanas en barsería menos claro e inútil cuando lo tienes return. Lo que hace barque las returndeclaraciones sean tan favorables es que está escrito en un estilo imperativo. De hecho, las variables booleanas representan los cambios de "estado" evitados en un estilo funcional.

Es realmente difícil reescribir baren estilo funcional, porque es solo un pseudocódigo, pero la idea es algo como esto:

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

El whilebucle sería el más difícil de reescribir, porque está controlado por cambios de estado a a.

La pérdida de velocidad causada por una llamada a returnes insignificante, pero la eficiencia obtenida al evitar returny reescribir en un estilo funcional a menudo es enorme. Decirles a los nuevos usuarios que dejen de usarlo returnprobablemente no ayudará, pero guiarlos hacia un estilo funcional dará resultado.


@Paul returnes necesario en un estilo imperativo porque a menudo desea salir de la función en diferentes puntos de un bucle. Un estilo funcional no usa bucles y, por lo tanto, no es necesario return. En un estilo puramente funcional, la llamada final es casi siempre el valor de retorno deseado.

En Python, las funciones requieren una returndeclaración. Sin embargo, si programó su función en un estilo funcional, es probable que solo tenga una returndeclaración: al final de su función.

Usando un ejemplo de otra publicación de StackOverflow, digamos que deseamos una función que devuelva TRUEsi todos los valores de un determinado xtienen una longitud impar. Podríamos usar dos estilos:

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

En un estilo funcional, el valor que se devuelve cae naturalmente en los extremos de la función. De nuevo, se parece más a una función matemática.

@GSee Las advertencias descritas en ?ifelseson definitivamente interesantes, pero no creo que estén tratando de disuadir el uso de la función. De hecho, ifelsetiene la ventaja de vectorizar automáticamente las funciones. Por ejemplo, considere una versión ligeramente modificada de foo:

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

Esta función funciona bien cuando length(a)es 1. Pero si reescribió foocon unifelse

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

Ahora foofunciona en cualquier longitud de a. De hecho, incluso funcionaría cuando aes una matriz. Devolver un valor con la misma forma que testes una característica que ayuda con la vectorización, no es un problema.

nograpes
fuente
No me queda claro por qué returnno encaja con un estilo funcional de programación. Si uno está programando imperativa o funcionalmente, en algún momento una función o subrutina necesita devolver algo. Por ejemplo, la programación funcional en python todavía requiere una returndeclaración. ¿Podría dar más detalles sobre este punto?
Paul Hiemstra
En esta situación, usar ifelse(a,a,b)es una manía mía. Parece que cada línea ?ifelseestá gritando, "no me uses en lugar de if (a) {a} else b". por ejemplo, "... devuelve un valor con la misma forma que test", "si yeso noson demasiado cortos, sus elementos se reciclan", "el modo del resultado puede depender del valor de test", "el atributo de clase del resultado se toma de testy puede ser inapropiado para los valores seleccionados de yesy no"
GSee
A segunda vista, foono tiene mucho sentido; siempre devolverá VERDADERO o b. Usarlo ifelsedevolverá 1 o varios VERDADEROS, y / o 1 o varios bs. Inicialmente, pensé que la intención de la función era decir "si alguna declaración es VERDADERA, devuelve algo, de lo contrario, devuelve otra cosa". No creo que debería ser vectorizado, porque entonces se convertiría en "devolver los elementos de un objeto que son verdaderas, y para todos los elementos que no son ciertas, el retorno b.
GSEE
22

Parece que sin return()eso es más rápido ...

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

____ EDITAR__ _ __ _ __ _ __ _ __ _ ___

Procedo a otros benchmark ( benchmark(fuu(x),foo(x),replications=1e7)) y el resultado se invierte ... Lo intentaré en un servidor.

Alan
fuente
¿Podría comentar la razón por la cual ocurre esta diferencia?
Paul Hiemstra
44
La respuesta de @PaulHiemstra Petr cubre una de las principales razones de esto; dos llamadas al usar return(), una si no lo hace. Es totalmente redundante al final de una función, ya que function()devuelve su último valor. Solo notará esto en muchas repeticiones de una función en la que no se hace mucho internamente, de modo que el costo se return()convierte en una gran parte del tiempo total de cálculo de la función.
Gavin Simpson, el
13

Un problema con no poner explícitamente 'return' al final es que si uno agrega declaraciones adicionales al final del método, de repente el valor de retorno es incorrecto:

foo <- function() {
    dosomething()
}

Esto devuelve el valor de dosomething().

Ahora llegamos al día siguiente y agregamos una nueva línea:

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

Queríamos que nuestro código devolviera el valor de dosomething(), pero ya no lo hace.

Con un retorno explícito, esto se vuelve realmente obvio:

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

Podemos ver que hay algo extraño en este código y solucionarlo:

foo <- function() {
    dosomething2()
    return( dosomething() )
}
Hugh Perkins
fuente
1
Sí, en realidad encuentro que un return explícito () es útil al depurar; una vez que se limpia el código, su necesidad es menos convincente y prefiero la elegancia de no tenerlo ...
PatrickT
Pero esto no es realmente un problema en el código real, es puramente teórico. El código que podría sufrir esto tiene un problema mucho mayor: un flujo de código oscuro y frágil que es tan poco obvio que las simples adiciones lo rompen.
Konrad Rudolph
@KonradRudolph Creo que estás haciendo un tipo de escocés no verdadero en él ;-) "Si esto es un problema en tu código, ¡eres un mal programador!". Realmente no estoy de acuerdo. Creo que si bien puede salirse con la suya al tomar atajos en pequeños trozos de código, donde conoce cada línea de memoria, esto volverá a morderle a medida que su código se agrande.
Hugh Perkins
2
@HughPerkins No es un verdadero escocés ; más bien, es una observación empírica sobre la complejidad del código, respaldada por décadas de mejores prácticas de ingeniería de software: mantenga las funciones individuales cortas y el flujo del código obvio. Y omitir returnno es un atajo, es el estilo apropiado en la programación funcional. El uso de returnllamadas a funciones innecesarias es una instancia de programación de culto de carga .
Konrad Rudolph el
Bueno ... no veo cómo esto le impide agregar algo después de su returndeclaración y no darse cuenta de que no se ejecutará. También podría agregar un comentario después del valor que desea devolver, por ejemplodosomething() # this is my return value, don't add anything after it unless you know goddam well what you are doing
lebatsnok
10

Mi pregunta es: ¿por qué no llamar returnmás rápido?

Es más rápido porque returnes una función (primitiva) en R, lo que significa que su uso en código incurre en el costo de una llamada a función. Compare esto con la mayoría de los otros lenguajes de programación, donde returnes una palabra clave, pero no una llamada de función: no se traduce en ninguna ejecución de código de tiempo de ejecución.

Dicho esto, llamar a una función primitiva de esta manera es bastante rápido en R, y llamar returnconlleva una sobrecarga minúscula. Este no es el argumento a favor de la omisión return.

o mejor, y por lo tanto preferible?

Porque no hay razón para usarlo.

Porque es redundante y no agrega redundancia útil .

Para ser claros: la redundancia a veces puede ser útil . Pero la mayoría de la redundancia no es de este tipo. En cambio, es del tipo que agrega desorden visual sin agregar información: es el equivalente de programación de una palabra de relleno o chartjunk ).

Considere el siguiente ejemplo de un comentario explicativo, que se reconoce universalmente como mala redundancia porque el comentario simplemente parafrasea lo que el código ya expresa:

# Add one to the result
result = x + 1

Usar returnen R cae en la misma categoría, porque R es un lenguaje de programación funcional y en R cada llamada a función tiene un valor . Esta es una propiedad fundamental de R. Y una vez que vea el código R desde la perspectiva de que cada expresión (incluida cada llamada de función) tiene un valor, la pregunta se convierte en: "¿por qué debería usar return?" Debe haber una razón positiva, ya que el valor predeterminado es no usarlo.

Una de estas razones positivas es señalar la salida anticipada de una función, por ejemplo, en una cláusula de guardia :

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

Este es un uso válido y no redundante de return. Sin embargo, tales cláusulas de protección son raras en R en comparación con otros lenguajes, y dado que cada expresión tiene un valor, una regular ifno requiere return:

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

Incluso podemos reescribir fasí:

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

... donde if (cond) expres lo mismo que if (cond) expr else NULL.

Finalmente, me gustaría prevenir tres objeciones comunes:

  1. Algunas personas argumentan que el uso returnagrega claridad, porque indica "esta función devuelve un valor". Pero como se explicó anteriormente, cada función devuelve algo en R. Pensar returnque un marcador de devolución de un valor no es solo redundante, es activamente engañoso .

  2. Relacionado, el Zen de Python tiene una pauta maravillosa que siempre debe seguirse:

    Explícito es mejor que implícito.

    ¿Cómo la caída redundante returnno viola esto? Porque el valor de retorno de una función en un lenguaje funcional siempre es explícito: es su última expresión. Este es de nuevo el mismo argumento sobre lo explícito frente a la redundancia.

    De hecho, si desea ser explícito, úselo para resaltar la excepción a la regla: funciones de marca que no devuelven un valor significativo, que solo se llaman por sus efectos secundarios (como cat). Excepto R tiene un mejor marcador que returnpara este caso: invisible. Por ejemplo, escribiría

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
  3. ¿Pero qué hay de las funciones largas? ¿No será fácil perder la noción de lo que se devuelve?

    Dos respuestas: primero, no realmente. La regla es clara: la última expresión de una función es su valor. No hay nada que seguir.

    Pero lo más importante, el problema en las funciones largas no es la falta de returnmarcadores explícitos . Es el duración de la función . Las funciones largas casi (?) Siempre violan el principio de responsabilidad única e incluso cuando no lo hacen, se beneficiarán de ser separadas para facilitar la lectura.

Konrad Rudolph
fuente
Tal vez debería agregar que algunas personas abogan por usarlo returnpara hacerlo más similar a otros idiomas. Pero ese es un mal argumento: otros lenguajes de programación funcionales tienden a no usarse returntampoco. Solo los lenguajes imperativos , donde no todas las expresiones tienen un valor, lo usan.
Konrad Rudolph el
Llegué a esta pregunta con la opinión de que el uso de returnsoportes es explícitamente mejor, y leí su respuesta con críticas completas. Su respuesta me ha llevado a reflexionar sobre esa opinión. Creo que la necesidad de usar returnexplícitamente (al menos en mi propio caso) está vinculada a la necesidad de poder revisar mejor mis funciones en un momento posterior. Con la idea de que mis funciones podrían ser demasiado complejas, ahora puedo ver que un objetivo para mejorar mi estilo de programación sería tratar de estructurar los códigos para mantener la explicidad sin un return. ¡Gracias por esas reflexiones e ideas!
Kasper Thystrup Karstensen
6

Pienso returnen un truco. Como regla general, el valor de la última expresión evaluada en una función se convierte en el valor de la función, y este patrón general se encuentra en muchos lugares. Todos los siguientes evalúan a 3:

local({
1
2
3
})

eval(expression({
1
2
3
}))

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

Lo que returnno es realmente devolver un valor (esto se hace con o sin él) sino "romper" la función de manera irregular. En ese sentido, es el equivalente más cercano de la declaración GOTO en R (también hay break y next). Yo uso returnmuy raramente y nunca al final de una función.

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

... esto se puede reescribir como if(a) a else bque es mucho mejor legible y menos cursivo. No hay necesidad de returnnada aquí. Mi caso prototípico de uso de "retorno" sería algo así como ...

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

En general, la necesidad de muchos retornos sugiere que el problema es feo o está mal estructurado.

<>

return realmente no necesita una función para funcionar: puede usarla para salir de un conjunto de expresiones que se evaluarán.

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)
lebatsnok
fuente
Hoy encontré un caso en el que uno realmente puede necesitar return(mi feo ejemplo anterior es muy artificial): supongamos que necesita probar si un valor es NULLo NA: en estos casos, devuelve una cadena vacía, de lo contrario devuelve el charactervalor. Pero una prueba de is.na(NULL)da un error, por lo que parece que solo se puede hacer if(is.null(x)) return("")y luego continuar if(is.na(x)) ...... (Se puede usar en length(x)==0lugar de, is.null(x)pero aún así, no es posible usarlo length(x)==0 | is.na(x)si xes así NULL)
Lebatsnok
1
Esto se debe a que usó |(OR vectorizado donde se evalúan ambos lados) en lugar de ||(OR cortocircuito, no vectorizado, donde los predicados se evalúan a su vez). Considere if (TRUE | stop()) print(1)versusif (TRUE || stop()) print(1)
asac
2

return puede aumentar la legibilidad del código:

foo <- function() {
    if (a) return(a)       
    b     
}
Eldar Agalarov
fuente
3
Tal vez pueda, en eso. Pero no lo hace en tu ejemplo. En cambio, oscurece (o más bien, compleja) el flujo del código.
Konrad Rudolph
1
su función se puede simplificar a: foo <- function() a || b(que es IMO más legible; en cualquier caso, no hay legibilidad "pura" sino legibilidad en la opinión de alguien: hay personas que dicen que el lenguaje ensamblador es perfectamente legible)
lebatsnok
1

El argumento de la redundancia ha surgido mucho aquí. En mi opinión, esa no es razón suficiente para omitir return(). La redundancia no es automáticamente una mala cosa. Cuando se usa estratégicamente, la redundancia hace que el código sea más claro y más sostenible.

Considere este ejemplo: los parámetros de función a menudo tienen valores predeterminados. Por lo tanto, especificar un valor que sea el mismo que el predeterminado es redundante. Excepto que hace obvio el comportamiento que espero. No es necesario abrir la página de manual de funciones para recordarme cuáles son los valores predeterminados. Y no se preocupe por una versión futura de la función que cambie sus valores predeterminados.

Con una penalización de rendimiento insignificante por llamar return()(según los puntos de referencia publicados aquí por otros) se trata de estilo en lugar de correcto e incorrecto. Para que algo esté "mal", debe haber una clara desventaja, y nadie aquí ha demostrado satisfactoriamente que incluir u omitir return()tenga una desventaja constante. Parece muy específico de caso y específico de usuario.

Así que aquí es donde estoy parado en esto.

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

Me siento incómodo con las variables "huérfanas" como en el ejemplo anterior. ¿ abcdSería parte de una declaración que no terminé de escribir? ¿Es un remanente de un empalme / edición en mi código y necesita ser eliminado? ¿Accidentalmente pegué / moví algo de otro lugar?

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

Por el contrario, este segundo ejemplo me hace obvio que es un valor de retorno previsto, en lugar de un accidente o un código incompleto. Para mí, esta redundancia no es absolutamente inútil.

Por supuesto, una vez que la función esté terminada y funcionando, podría eliminar la devolución. Pero eliminarlo es en sí mismo un paso adicional redundante y, en mi opinión, es más inútil que incluirlo return()en primer lugar.

Dicho todo esto, no uso return()en resumen las funciones de una línea sin nombre. Allí constituye una gran fracción del código de la función y, por lo tanto, causa un desorden visual que hace que el código sea menos legible. Pero para funciones más grandes formalmente definidas y nombradas, lo uso y probablemente continuaré haciéndolo.

cymon
fuente
"¿Abcd iba a ser parte de una declaración que no terminé de escribir?" - Sin embargo, ¿en qué se diferencia esto de cualquier otra expresión que escriba? Esto, creo que es el núcleo de nuestro desacuerdo. Tener un soporte variable por sí solo puede ser peculiar en un lenguaje de programación imperativo, pero es completamente normal y esperado en un lenguaje de programación funcional. El problema, afirmo, es simplemente que no estás familiarizado con la programación funcional (el hecho de que hables de "declaraciones" en lugar de "expresiones" refuerza esto).
Konrad Rudolph el
Es diferente porque cada otra declaración generalmente hace algo de una manera más obvia: es una asignación, una comparación, una llamada a la función ... Sí, mis primeros pasos de codificación fueron en idiomas imperativos y todavía uso idiomas imperativos. Tener pistas visuales uniformes en todos los idiomas (donde los idiomas lo permitan) hace que mi trabajo sea más fácil. A return()en R no cuesta nada. Es objetivamente redundante, pero ser "inútil" es su juicio subjetivo. Redundante e inútil no son necesariamente sinónimos. Ahí es donde no estamos de acuerdo.
cymon
Además, no soy ingeniero de software ni informático. No leas demasiados matices a mi uso de la terminología.
cymon
Solo para aclarar: “Redundante e inútil no son necesariamente sinónimos. Ahí es donde no estamos de acuerdo. - No, estoy totalmente de acuerdo con eso, y explícitamente hice ese punto en mi respuesta. La redundancia puede ser útil o incluso crucial . Pero esto necesita ser mostrado activamente, no asumido. Entiendo tu argumento de por qué crees que esto es válido return, y aunque no estoy convencido, creo que es potencialmente válido (definitivamente está en un idioma imperativo ... creo que no se traduce a lenguajes funcionales).
Konrad Rudolph el