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

foo = function() {
  return(value)
}

相反,他建议:

foo = function() {
  value
}

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

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

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

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


当前回答

这是一个有趣的讨论。我认为@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'放在末尾的一个问题是,如果在方法的末尾添加了额外的语句,返回值突然就错了:

foo <- function() {
    dosomething()
}

返回dosomething()的值。

现在我们来到第二天,添加了新的一行:

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

我们希望代码返回dosomething()的值,但它不再这样做了。

通过显式返回,这变得非常明显:

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

我们可以看到这段代码有些奇怪,并修复它:

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

问题是:为什么不(显式地)更快或更好地调用return,从而更可取?

R文档中没有做这样假设的语句。 主页?'function'说:

function( arglist ) expr
return(value)

不调用return会更快吗?

function()和return()都是基本函数,即使不包括return()函数,function()本身也会返回上一次求值。

将return()作为. primitive ('return')调用,并将最后一个值作为参数将完成相同的工作,但需要多调用一次。因此这个(通常)不必要的. primitive ('return')调用可以获取额外的资源。 然而,简单的测量表明所产生的差异非常小,因此不能成为不使用显式回报的原因。下图是用这种方法选择的数据绘制的:

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

上面的图片在你的平台上可能略有不同。 根据测量的数据,返回对象的大小没有造成任何差异,重复的次数(即使缩放)只产生非常小的差异,这在真实的数据和真实的算法中无法计算或使您的脚本运行得更快。

不调用return会更好吗?

Return是一个很好的工具,可以清晰地设计代码的“叶子”,在这里例程应该结束,跳出函数并返回值。

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

这取决于程序员的策略和编程风格,他使用什么风格,他可以不使用return(),因为它不是必需的。

R核心程序员使用这两种方法。使用或不使用显式return(),因为可以在'base'函数的源中找到。

很多时候只使用return()(无参数)返回NULL来有条件地停止函数。

不清楚它是否更好,因为使用R的标准用户或分析师无法看到真正的区别。

我的观点是这个问题应该是:使用来自R实现的显式返回是否有危险?

或者,更好的是,编写函数代码的用户应该总是问:在函数代码中不使用显式返回(或将要返回的对象作为代码分支的最后一个叶)会有什么影响?

似乎没有return()更快…

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 __________________

我继续到其他基准(基准(fuu(x),foo(x),复制=1e7)),结果是相反的…我在服务器上试试。

这是一个有趣的讨论。我认为@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形状相同的值是一个有助于向量化的特性,不是问题。

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

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