来自R核心团队的Simon Urbanek(我相信)推荐用户在功能结束时显式调用return
(他的评论已被删除)的一段时间I got rebuked:< / p>
foo = function() {
return(value)
}
相反,他建议:
foo = function() {
value
}
可能在这种情况下需要:
foo = function() {
if(a) {
return(a)
} else {
return(b)
}
}
他的评论阐明了为什么不调用return
,除非严格要求是好事,但这被删除了。
我的问题是:为什么不更快或更好地致电return
,因此更可取?
答案 0 :(得分:118)
问题是:为什么不(明确地)调用返回更快或更好,因此更可取?
R文档中没有任何声明做出这样的假设 Tha man page?'function'说:
function( arglist ) expr
return(value)
不调用退货会更快吗?
function()
和return()
都是原始函数,function()
本身即使不包含return()
函数也会返回最后评估的值。
使用最后一个值作为参数调用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
是清楚地设计代码“叶子”的好工具,其中例程应该结束,跳出函数并返回值。
# 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实现的显式回报是否有任何危险?
或者,或许更好,用户编写函数代码应始终询问:使用显式返回(或将对象作为代码分支的最后一个叶子返回放置)中不的影响是什么功能代码?
答案 1 :(得分:85)
如果每个人都同意
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语句重写会更加困难:它需要多个break
s和一个复杂的布尔变量系统来传播它们。所有这些都说单一返回规则与R不匹配。所以如果你需要在函数体的某些地方使用return
,为什么不一致并在任何地方使用它?
我不认为速度参数是有效参数。当您开始查看实际执行某些操作的函数时,0.8微秒的差异就不算什么了。我能看到的最后一件事是打字少了,但是嘿,我并不懒惰。
答案 2 :(得分:22)
似乎没有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)
),结果反过来......我会尝试服务器。
答案 3 :(得分:22)
这是一个有趣的讨论。我认为@ flodel的例子很棒。但是,我认为这说明了我的观点(@koshke在评论中提到这一点) return
在您使用命令式而不是功能>时有意义编码风格。
不要强调这一点,但我会像这样重写foo
:
foo = function() ifelse(a,a,b)
功能样式可以避免状态更改,例如存储output
的值。在这种风格中,return
不合适; foo
看起来更像是一种数学函数。
我同意@flodel:在bar
中使用错综复杂的布尔变量系统会不那么明确,而在return
时则毫无意义。使bar
如此适合return
语句的原因在于它是以命令式的方式编写的。实际上,布尔变量表示功能样式中避免的“状态”变化。
在功能样式中重写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
可能无济于事,但引导他们使用功能样式将会有所收获。
@Paul return
在命令式样式中是必需的,因为您经常希望在循环中的不同点退出函数。功能样式不使用循环,因此不需要return
。在纯函数风格中,最终调用几乎总是所需的返回值。
在Python中,函数需要return
语句。但是,如果您以函数式编写函数,则可能只有一个return
语句:在函数结束时。
使用另一个StackOverflow帖子中的示例,假设我们想要一个函数,如果给定TRUE
中的所有值都有奇数长度,则该函数返回x
。我们可以使用两种风格:
# 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时,此函数正常工作。但如果您使用foo
重写了ifelse
foo = function (a) ifelse(a,a,b)
现在foo
适用于a
的任意长度。事实上,当a
是一个矩阵时它甚至会起作用。返回与test
形状相同的值是一个有助于矢量化而不是问题的特征。
答案 4 :(得分:13)
最后没有明确地放置'return'的问题是如果在方法结束时添加了额外的语句,突然返回值是错误的:
foo <- function() {
dosomething()
}
返回dosomething()
的值。
现在我们来到第二天并添加一个新行:
foo <- function() {
dosomething()
dosomething2()
}
我们希望我们的代码返回dosomething()
的值,但不再是它。
通过明确的回报,这变得非常明显:
foo <- function() {
return( dosomething() )
dosomething2()
}
我们可以看到这段代码有些奇怪,并修复它:
foo <- function() {
dosomething2()
return( dosomething() )
}
答案 5 :(得分:5)
我认为return
是一个技巧。作为一般规则,函数中计算的最后一个表达式的值将成为函数的值 - 并且在许多地方都可以找到此常规模式。以下所有评估为3:
local({
1
2
3
})
eval(expression({
1
2
3
}))
(function() {
1
2
3
})()
return
所做的并不是返回一个值(无论是否完成),但以不规则的方式“突破”该函数。从这个意义上说,它是R中最接近的GOTO语句(也有中断和下一个)。我很少使用return
,而且从不在函数的末尾。
if(a) {
return(a)
} else {
return(b)
}
...这可以改写为if(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")
}
一般来说,需要多次回报表明问题要么是丑陋的,要么是结构不合理。
&LT;&GT;
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)
答案 6 :(得分:5)
我的问题是:为什么不更快地拨打
return
它更快,因为return
是R中的(原始)函数,这意味着在代码中使用它会产生函数调用的开销。将此与大多数其他编程语言进行比较,在其他编程语言中,return
是关键字,而不是函数调用:它不会转换为任何运行时代码执行。
也就是说,在R中以这种方式调用原始函数非常快,而调用return
则导致开销很小。这不是省略return
的理由。
还是更好,因此更可取?
因为没有理由 使用它。
因为它是多余的,并且没有添加有用的冗余。
要明确:redundancy can sometimes be useful。但是大多数冗余不是这种类型的。相反,它是在不添加信息的情况下添加visual clutter的类型:相当于filler word或chartjunk)的编程方式。
考虑以下解释性注释示例,该注释通常被认为是不好的冗余,因为该注释只是解释代码已经表达的内容:
# Add one to the result
result = x + 1
在R中使用return
属于同一类别,因为R是functional programming language,并且在R中每个函数调用都有一个值。这是R的基本属性。一旦您从每个表达式(包括每个函数调用)都有一个值的角度看到R代码,问题就会变成:“为什么应该我使用return
吗?”肯定有一个积极的原因,因为默认设置是不使用它。
一个积极的原因是发出信号提前退出功能,例如在guard clause中表示:
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
相同。
最后,我想阻止三个常见的反对意见:
有人认为使用return
可以增加清晰度,因为它表示“此函数返回一个值”。但是如上所述,每个函数都会在R中返回某些内容。将return
视为返回值的标记不仅是多余的,而且还会误导。
Zen of Python的相关指导非常出色,应始终遵循:
显式优于隐式。
如何丢弃冗余return
不会违反此规定?因为函数在函数式语言中的返回值始终是明确的:这是它的最后一个表达式。关于显式 vs 冗余,这再次是同一论点。
实际上,如果您想要明确性,可以使用它来突出显示规则的异常:标记不返回有意义的值的函数,这些函数仅出于它们的副作用而被调用(例如为cat
)。在这种情况下,除了R具有比return
更好的标记:invisible
。例如,我会写
save_results = function (results, file) {
# … code that writes the results to a file …
invisible()
}
但是长函数呢?难道不容易追回所退回的物品吗?
两个答案:首先,不是真的。规则很明确:函数的最后一个表达式是其值。没有什么可追踪的。
但更重要的是,长函数的问题不是缺少明确的return
标记。这是函数的长度。长函数几乎(?)始终会违反single responsibility principle,即使不这样做,它们也会因可读性的分解而受益。
答案 7 :(得分:2)
return
可以提高代码的可读性:
foo <- function() {
if (a) return(a)
b
}
答案 8 :(得分:1)
在这里,关于冗余的争论很多。我认为这还不足以省略return()
。
冗余并不是一件坏事。如果策略性地使用冗余,则可使代码更清晰,更可维护。
请考虑以下示例:函数参数通常具有默认值。因此,指定与默认值相同的值是多余的。除非它使我预期的行为显而易见。无需调出功能手册页即可提醒自己默认值是什么。不用担心该功能的未来版本会更改其默认值。
调用return()
的性能损失可忽略不计(根据其他人在此处发布的基准),它归结为风格,而不是对与错。对于某些“错误”,必须有明显的缺点,并且这里没有人令人满意地证明包含或省略return()
始终是缺点。似乎是特定于案例和特定于用户的。
这就是我坚持的立场。
function(){
#do stuff
...
abcd
}
我不喜欢上面的例子中的“孤立”变量。 abcd
是否会成为我未完成撰写的声明的一部分?它是我的代码中拼接/编辑的残余,需要删除吗?我是否从其他地方不小心粘贴/移动了东西?
function(){
#do stuff
...
return(abdc)
}
相比之下,第二个示例对我来说显然是一个预期的返回值,而不是某些意外或不完整的代码。对我来说,这种冗余绝对不是没有用的。
当然,一旦函数完成并正常工作,我就可以删除返回值。但是删除它本身是多余的多余步骤,而且我认为比起首先包含return()
来说,它没有更多用处。
所有这些,我不会在简短的未命名单线函数中使用return()
。在那里,它占了函数代码的很大一部分,因此,大多数情况下会导致视觉混乱,使代码难以辨认。但是对于较大的正式定义和命名的函数,我会使用它,并且可能会继续这样做。