在函数中显式调用return还是不行

前段时间, 我被 R核心团队的Simon Urbanek 斥责 (我相信)build议用户在函数结尾处明确调用return (他的评论已被删除):

 foo = function() { return(value) } 

相反,他build议:

 foo = function() { value } 

可能在这样的情况下,它是必需的:

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

他的评论阐明了为什么不调用return除非严格需要是件好事,但是这被删除了。

我的问题是:为什么不调用更快或更好的return ,从而更好?

问题是:为什么不(明确)调用返回更快或更好,从而更好?

在R文档中没有这样的假设。
那个人的页面?'function'说:

 function( arglist ) expr return(value) 

没有调用return会更快吗?

function()return()都是原始函数, function()本身返回最后的计算值,即使不包含return()函数。

调用return()作为.Primitive('return')与最后一个值作为参数将做同样的工作,但需要更多的一个电话。 所以这(通常)不必要的.Primitive('return')调用可以吸引额外的资源。 然而,简单的测量表明,由此产生的差异非常小,因此不能成为不使用明确收益的原因。 下面的图是从这样select的数据中创build的:

 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 

功能经过时间比较

上面的图片可能会在你的平台上略有不同。 根据测量数据,返回对象的大小没有任何差别,重复次数(即使按比例放大)只是非常小的差异,实际上用实际数据和实际algorithm计算出来的重复次数是无法计算的,脚本运行得更快。

不打电话回来更好吗?

Return是清晰地devise例程应该结束的代码“离开”的好工具,跳出函数并返回值。

 # 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核心程序员使用两种方法,即。 有和没有显式返回(),因为它可以find“基”function的来源。

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

作为标准的用户或使用R的分析人员看不出真正的差异,目前尚不清楚。

我的意见是这个问题应该是: 使用来自R实施的明确回报有没有危险?

或者,也许更好,用户编写函数代码应该总是问:函数代码使用显式返回(或将对象作为代码分支的最后一个叶返回)的效果是什么?

如果每个人都同意这一点

  1. 在函数体的结尾处不需要return
  2. 不使用return稍微快一点(根据@ Alan的testing,4.3微秒与5.1)

我们是否应该在函数结束时停止使用return ? 我当然不会,我想解释为什么。 我希望听到其他人是否同意我的看法。 我很抱歉,如果这不是OP的直接答案,更像是一个长期的主观评论。

我不使用return主要问题是,正如Paul指出的那样,函数体中还有其他地方可能需要它。 如果你不得不在函数中间的某个地方使用return ,为什么不把所有的return语句都显式化呢? 我讨厌不一致。 另外我觉得代码读得更好; 可以扫描该function并轻松查看所有出口点和值。

保罗使用这个例子:

 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语句来重写会更困难:它需要多个break和一个复杂的布尔variables系统来传播它们。 所有这一切都说R的单一返回规则不能很好地发挥作用。所以,如果你需要在你的函数体的某些地方使用return ,为什么不能保持一致并在任何地方使用呢?

我不认为速度论证是有效的。 当您开始查看实际执行某些操作的函数时,0.8微秒的差异就不算什么了。 我能看到的最后一件事情是打字less,但嘿,我不懒惰。

看来,没有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 

____ 编辑 __ _ _ _ _ _ _ _ _ _ _ _ _ ___

我进入其他基准( benchmark(fuu(x),foo(x),replications=1e7) ),结果是相反的…我会尝试在服务器上。

这是一个有趣的讨论。 我认为@ flodel的例子非常好。 不过,我认为这说明了我的观点(@koshke在评论中提到这一点), 当你使用命令而不是function性编码风格时return是有意义的

不要轻视这一点,但我会重写这样的foo

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

function风格避免了状态变化,如存储output的值。 在这种风格中, return是不合适的。 foo看起来更像是一个math函数。

我同意@ flodel:在bar使用一个错综复杂的布尔variables系统将不那么清晰,并且在return时没有意义。 是什么让bar顺利return陈述是它是写在一个迫切的风格。 事实上,布尔variables表示在function风格中避免的“状态”变化。

在function风格上重写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循环将是最难重写的,因为它是由状态变化控制a

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


@Paul return在命令式风格中是必须的,因为你经常想要在循环中的不同点退出函数。 function样式不使用循环,因此不需要return 。 在一个纯粹的function风格中,最后的调用几乎总是期望的返回值。

在Python中,函数需要return语句。 但是,如果以function样式对函数进行编程,则可能只有一个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) 

在function风格中,要返回的值自然落在函数的末尾。 再次,它看起来更像是一个math函数。

@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是matrix时它甚至会起作用。 返回值与test相同的形状是一个有助于vector化的function,而不是一个问题。

如果在方法末尾添加额外的语句,突然返回值是错误的:

 foo <- function() { dosomething() } 

这将返回dosomething()的值。

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

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

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

有了明确的回报,这变得非常明显:

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

我们可以看到,这个代码有一些奇怪的地方,并修复它:

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

我觉得return是一个把戏。 作为一般规则,函数中最后一个expression式的值将成为函数的值 – 而这个通用模式在很多地方都可以find。 以下全部评估为3:

 local({ 1 2 3 }) eval(expression({ 1 2 3 })) (function() { 1 2 3 })() 

什么return不是真的返回一个值(这是做或不做),而是以不规则的方式“爆发”的function。 从这个意义上讲,它是R中最接近的GOTO语句(也有break和next)。 我很less使用return ,也不会在函数结尾使用。

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

…这可以被重写,就if(a) a else b更好的可读性和更less的curl。 这里根本不需要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并不是真的需要一个函数来工作:你可以使用它来分解一系列要评估的expression式。

 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可以提高代码的可读性:

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