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

foo = function() {
  return(value)
}

相反,他建议:

foo = function() {
  value
}

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

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

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

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


似乎没有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)),结果是相反的…我在服务器上试试。


问题是:为什么不(显式地)更快或更好地调用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在函数体的末尾是不必要的 不使用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微秒的差别根本不算什么,当你开始研究真正有功能的函数时。我能看到的最后一件事是打字减少了,但嘿,我不懒。


这是一个有趣的讨论。我认为@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() )
}

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

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

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

我的问题是:为什么不调用返回更快

它更快,因为return是R中的(原语)函数,这意味着在代码中使用它会导致函数调用的代价。与大多数其他编程语言相比,其中return是关键字,而不是函数调用:它不转换为任何运行时代码执行。

也就是说,在R中以这种方式调用原语函数非常快,并且调用return的开销非常小。这不是省略return的理由。

还是更好,因此更可取?

因为没有理由使用它。

因为它是冗余的,它没有增加有用的冗余。

需要明确的是:冗余有时是有用的。但大多数冗余都不是这样的。相反,它是那种在不增加信息的情况下增加视觉混乱的东西:它相当于一个填充词或图表垃圾)。

考虑下面的解释性注释的例子,它被普遍认为是糟糕的冗余,因为注释只是解释了代码已经表达的内容:

# Add one to the result
result = x + 1

在R中使用return也属于同一类别,因为R是一种函数式编程语言,在R中每个函数调用都有一个值。这是R的一个基本属性,一旦你从每个表达式(包括每个函数调用)都有一个值的角度看R代码,问题就变成了:“为什么我要使用return?”需要有一个积极的理由,因为默认是不使用它。

其中一个积极的原因是在一个guard子句中提早退出函数:

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

这是一个有效的、非冗余的return用法。然而,与其他语言相比,这样的保护子句在R中很少,而且由于每个表达式都有一个值,正则if不需要return:

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

我们甚至可以这样重写f:

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

这里if (cond) expr与if (cond) expr else NULL相同。

最后,我想先说三个常见的反对意见:

Some people argue that using return adds clarity, because it signals “this function returns a value”. But as explained above, every function returns something in R. Thinking of return as a marker of returning a value isn’t just redundant, it’s actively misleading. Relatedly, the Zen of Python has a marvellous guideline that should always be followed: Explicit is better than implicit. How does dropping redundant return not violate this? Because the return value of a function in a functional language is always explicit: it’s its last expression. This is again the same argument about explicitness vs redundancy. In fact, if you want explicitness, use it to highlight the exception to the rule: mark functions that don’t return a meaningful value, which are only called for their side-effects (such as cat). Except R has a better marker than return for this case: invisible. For instance, I would write save_results = function (results, file) { # … code that writes the results to a file … invisible() } But what about long functions? Won’t it be easy to lose track of what is being returned? Two answers: first, not really. The rule is clear: the last expression of a function is its value. There’s nothing to keep track of. But more importantly, the problem in long functions isn’t the lack of explicit return markers. It’s the length of the function. Long functions almost (?) always violate the single responsibility principle and even when they don’t they will benefit from being broken apart for readability.


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

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

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

这就是我的立场。

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

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

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

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

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

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