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

foo = function() {
  return(value)
}

相反,他建议:

foo = function() {
  value
}

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

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

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

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


当前回答

问题是:为什么不(显式地)更快或更好地调用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()的理由。 冗余并不一定是坏事。如果有策略地使用冗余,可以使代码更清晰、更可维护。

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

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

这就是我的立场。

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

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

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

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

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

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

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

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

没有显式地将'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实现的显式返回是否有危险?

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

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