前一段时间,我被R核心团队的Simon Urbanek指责,因为我建议用户在函数的末尾显式调用return(他的评论被删除了):

foo = function() {
  return(value)
}

相反,他建议:

foo = function() {
  value
}

可能在这种情况下是需要的:

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

他的评论解释了为什么不调用return是一件好事,但这条被删除了。

我的问题是:为什么不调用返回更快或更好,因此更可取?


当前回答

我认为回报是一种诡计。作为一般规则,在函数中求值的最后一个表达式的值成为该函数的值——这种一般模式在许多地方都可以找到。所有这些都是3:

local({
1
2
3
})

eval(expression({
1
2
3
}))

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

return所做的并不是真正返回一个值(不管有没有它),而是以一种不规则的方式“打破”函数。从这个意义上说,它是R中最接近的GOTO语句(也有break和next)。我很少使用return,而且从不在函数的末尾使用。

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

... 这可以重写为(a) a else b,这是更好的可读性和更少的花括号。在这里根本不需要返回。我使用“return”的典型例子是这样的……

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

一般来说,对大量回报的需求表明,这个问题要么是丑陋的,要么是结构糟糕的。

(编辑)

Return实际上不需要函数来工作:您可以使用它来拆分一组要求值的表达式。

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)

其他回答

这是一个有趣的讨论。我认为@flodel的例子很好。然而,我认为这说明了我的观点(@koshke在评论中提到了这一点),当您使用命令式而不是函数式编码风格时,返回是有意义的。

我不想赘述这一点,但是我会像这样重写foo:

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

函数式风格避免了状态更改,比如存储输出值。在这种风格下,回归是不合适的;Foo看起来更像一个数学函数。

我同意@flodel的观点:在酒吧中使用复杂的布尔变量系统会不太清楚,而且当你返回时毫无意义。使bar如此易于返回语句的原因是它是用命令式风格编写的。实际上,布尔变量表示以函数式风格避免的“状态”变化。

以函数式的方式重写bar是非常困难的,因为它只是伪代码,但其思想是这样的:

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

while循环是最难重写的,因为它是由变量的状态更改控制的。

调用return所造成的速度损失可以忽略不计,但是通过避免返回并以函数式风格重写所获得的效率通常是巨大的。告诉新用户停止使用return可能不会有帮助,但引导他们使用函数式风格会有回报。


@Paul return在命令式中是必要的,因为您经常希望在循环中的不同位置退出函数。函数式风格不使用循环,因此不需要return。在纯函数式风格中,最终调用几乎总是所需的返回值。

在Python中,函数需要返回语句。然而,如果你用函数式风格编写函数,你可能只有一个return语句:在函数的末尾。

使用另一篇StackOverflow帖子中的示例,假设我们想要一个函数,如果给定x中的所有值都是奇数长度,则该函数返回TRUE。我们可以使用两种样式:

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

在函数式样式中,返回的值自然落在函数的末尾。它看起来更像一个数学函数。

@GSee ?ifelse中列出的警告确实很有趣,但我不认为他们试图劝阻使用该功能。事实上,ifelse具有自动向量化函数的优点。例如,考虑稍微修改过的foo:

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

当length(a)为1时,此函数工作正常。但如果你用ifelse重写foo

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

现在foo适用于a的任何长度,事实上,它甚至适用于a是一个矩阵。返回与test形状相同的值是一个有助于向量化的特性,不是问题。

冗余的争论在这里出现了很多。在我看来,这不足以成为省略return()的理由。 冗余并不一定是坏事。如果有策略地使用冗余,可以使代码更清晰、更可维护。

考虑这个例子:函数参数通常有默认值。因此,指定与默认值相同的值是多余的。但这显然是我所期待的行为。不需要打开函数manpage来提醒自己默认值是什么。而且不用担心该函数的未来版本会改变其默认值。

调用return()的性能损失可以忽略不计(根据其他人在这里发布的基准测试),这取决于样式,而不是对与错。对于“错误”的东西,需要有一个明显的缺点,这里没有人满意地证明包含或省略return()具有一致的缺点。这似乎是非常具体的案例和用户。

这就是我的立场。

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

我对上面例子中的“孤儿”变量感到不舒服。abcd会成为我还没写完的声明的一部分吗?它是我的代码中拼接/编辑的残余,需要删除?我是否不小心从其他地方粘贴/移动了一些东西?

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

相比之下,第二个示例使我清楚地看到它是一个预期的返回值,而不是一些意外或不完整的代码。对我来说,这种冗余绝对不是无用的。

当然,一旦函数完成并工作,我可以删除返回。但是删除它本身就是一个多余的额外步骤,在我看来,它比首先包含return()更无用。

综上所述,我没有使用return()作为未命名的单行函数。在函数代码中,它占了很大一部分,因此主要导致视觉上的混乱,使代码更不容易读懂。但是对于更大的正式定义和命名的函数,我使用它,并且可能会继续使用。

我认为回报是一种诡计。作为一般规则,在函数中求值的最后一个表达式的值成为该函数的值——这种一般模式在许多地方都可以找到。所有这些都是3:

local({
1
2
3
})

eval(expression({
1
2
3
}))

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

return所做的并不是真正返回一个值(不管有没有它),而是以一种不规则的方式“打破”函数。从这个意义上说,它是R中最接近的GOTO语句(也有break和next)。我很少使用return,而且从不在函数的末尾使用。

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

... 这可以重写为(a) a else b,这是更好的可读性和更少的花括号。在这里根本不需要返回。我使用“return”的典型例子是这样的……

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

一般来说,对大量回报的需求表明,这个问题要么是丑陋的,要么是结构糟糕的。

(编辑)

Return实际上不需要函数来工作:您可以使用它来拆分一组要求值的表达式。

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在函数体的末尾是不必要的 不使用return稍微快一点(根据@Alan的测试,4.3微秒对5.1微秒)

我们是否应该在函数末尾停止使用return ?我当然不会,我想解释一下原因。我希望听到其他人是否同意我的观点。如果这不是对OP的直接回答,我很抱歉,而更像是一个很长的主观评论。

我不使用return的主要问题是,正如Paul指出的,在函数体的其他地方可能需要它。如果你被迫在函数中间的某个地方使用return,为什么不使所有的return语句显式呢?我讨厌前后矛盾。而且我认为代码读起来更好;你可以扫描这个函数,很容易看到所有的出口点和值。

保罗举了这个例子:

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

不幸的是,有人可能会指出,它可以很容易地重写为:

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

后一个版本甚至符合一些编程编码标准,提倡每个函数有一个return语句。我认为一个更好的例子是:

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

用一个return语句重写这要困难得多:它需要多个断点和一个复杂的布尔变量系统来传播它们。所有这些都说明单一返回规则不适用于r。因此,如果你需要在函数体的某些地方使用返回,为什么不保持一致,在所有地方都使用它呢?

我不认为速度的论点是有效的。0.8微秒的差别根本不算什么,当你开始研究真正有功能的函数时。我能看到的最后一件事是打字减少了,但嘿,我不懒。

返回可以增加代码的可读性:

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