在函数中显式调用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实施的明确回报有没有危险?
或者,也许更好,用户编写函数代码应该总是问:函数代码中不使用显式返回(或将对象作为代码分支的最后一个叶返回)的效果是什么?
如果每个人都同意这一点
- 在函数体的结尾处不需要
return
- 不使用
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 }