显式调用函数返回或不调用

Pau*_*tra 189 r

后面有一个,而我被指责西蒙Urbanek从R核心团队(我相信)用于推荐用户显式调用return的函数(他的评论被删除,虽然)的结尾:

foo = function() {
  return(value)
}
Run Code Online (Sandbox Code Playgroud)

相反,他建议:

foo = function() {
  value
}
Run Code Online (Sandbox Code Playgroud)

可能在这种情况下需要:

foo = function() {
 if(a) {
   return(a)
 } else {
   return(b)
 }
}
Run Code Online (Sandbox Code Playgroud)

他的评论揭示了为什么不打电话,return除非严格要求是好事,但这被删除了.

我的问题是:为什么不打电话return更快或更好,因此更可取?

Pet*_*usu 121

问题是:为什么不(明确地)调用返回更快或更好,因此更可取?

在R文档中没有做出这样的假设.
Tha man page?'function'说:

function( arglist ) expr
return(value)
Run Code Online (Sandbox Code Playgroud)

没有回电话会更快吗?

这两个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
Run Code Online (Sandbox Code Playgroud)

功能经过时间比较

上图可能在您的平台上略有不同.根据测量数据,返回对象的大小不会造成任何差异,重复次数(即使按比例放大)只会产生非常小的差异,实际数据和真实算法的实际单词无法计算或使您的脚本运行得更快.

没有回电话会更好吗?

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
> 
Run Code Online (Sandbox Code Playgroud)

这取决于程序员的策略和编程风格,他使用什么样的风格,他可以不使用return(),因为它不是必需的.

R核心程序员使用两种方法即.有和没有显式return(),因为它可以在'基础'函数的源中找到.

很多时候只使用return()(没有参数)在条件下返回NULL来条件地停止该函数.

目前尚不清楚它是否更好,因为使用R的标准用户或分析师无法看到真正的差异.

我的观点是,问题应该是:使用来自R实施的明确回报是否有任何危险?

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

  • "回归"的速度真的是你应该担心的最后一件事. (35认同)
  • 谢谢你的回答非常好.我相信使用`return`没有任何危险,它归结为程序员的偏好是否使用它. (4认同)
  • 我认为这是一个糟糕的答案.原因归结为对使用不必要的`return`函数调用的价值的根本不同意见.你*问*的问题不是你最后提出的问题.相反,它是:"为什么*应该*我使用冗余的`return`?它提供了哪些好处?"事实证明,答案是"不多",甚至"无论如何".你的回复没有理解这一点. (2认同)
  • @Dason 我在其他地方链接了一篇 Reddit 帖子,解释了为什么这个论点在这种情况下是有缺陷的。不幸的是,评论似乎每次都会被自动删除。简而言之,“return”就像一条明确的注释,上面写着“将 x 加 1”,旁边是一段执行“x = x + 2”的代码。换句话说,它的明确性是(a)完全无关,(b)它传达了*错误的*信息。因为 R 中“return”的语义纯粹是“中止这个函数”。它*不*与其他语言中的“return”含义相同。 (2认同)

flo*_*del 93

如果每个人都同意这一点

  1. return 在函数体的末尾没有必要
  2. 不使用return的速度略快(根据@Alan的测试,4.3微秒与5.1相比)

我们是否应该return在功能结束时停止使用?我当然不会,我想解释原因.我希望听到其他人是否赞同我的意见.如果它不是OP的直接答案,我会道歉,但更像是一个长期的主观评论.

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

保罗使用了这个例子:

foo = function() {
 if(a) {
   return(a)
 } else {
   return(b)
 }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,人们可以指出它很容易被重写为:

foo = function() {
 if(a) {
   output <- a
 } else {
   output <- b
 }
output
}
Run Code Online (Sandbox Code Playgroud)

后一版本甚至符合一些编程编码标准,这些标准主张每个函数有一个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)
}
Run Code Online (Sandbox Code Playgroud)

使用单个return语句重写将更加困难:它需要多个breaks和一个复杂的布尔变量系统来传播它们.所有这一切都说单一返回规则与R不能很好地协作.所以如果你需要return在函数体的某些地方使用,为什么不能保持一致并在任何地方使用它?

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

  • +1,在某些情况下显然需要`return`语句,正如@flodel所示.或者,有些情况下最好省略return语句,例如很多很多小函数调用.在所有其他情况下,比如说95%的情况下,如果一个人使用"返回",那么它并不重要,而是归结为偏好.我喜欢使用return,因为它更明确你的意思,因此更具可读性.也许这个讨论类似于`<-` vs` =`? (6认同)
  • 这是将R视为命令式编程语言,它不是:它是一种函数式编程语言.函数式编程的工作方式不同,使用`return`返回一个值是荒谬的,与写if(x == TRUE)`而不是`if(x)`相同. (6认同)
  • 您还将`foo`重写为`foo < - function(x)if if(a)a else b`(根据需要使用换行符).无需显式返回或中间值. (4认同)

Ala*_*lan 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
Run Code Online (Sandbox Code Playgroud)

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

我继续其他基准测试(benchmark(fuu(x),foo(x),replications=1e7)),结果反转...我会尝试服务器.

  • @PaulHiemstra Petr的答案涵盖了其中一个主要原因; 使用`return()`时调用两次,如果不调用则调用一次.函数结束时完全冗余,因为`function()`返回它的最后一个值.你只会在函数的许多重复中注意到这一点,其中内部没有做太多的事情,因此`return()`的成本成为函数总计算时间的很大一部分. (4认同)

nog*_*pes 22

这是一个有趣的讨论.我认为@ flodel的例子很棒.但是,我认为它说明了我的观点(并且@koshke在评论中提到了这一点),return当你使用命令而不是功能编码风格时是有意义的.

不要强调这一点,但我会改写foo如下:

foo = function() ifelse(a,a,b)
Run Code Online (Sandbox Code Playgroud)

功能样式可以避免状态更改,例如存储值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.
}
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

在函数式中,要返回的值自然落在函数的末尾.再次,它看起来更像是一个数学函数.

@GSee中列出的警告?ifelse肯定很有意思,但我不认为他们试图劝阻使用该功能.实际上,ifelse具有自动矢量化功能的优点.例如,考虑稍微修改过的版本foo:

foo = function(a) { # Note that it now has an argument
 if(a) {
   return(a)
 } else {
   return(b)
 }
}
Run Code Online (Sandbox Code Playgroud)

这个函数在length(a)1 时工作正常.但是如果你foo用一个改写了ifelse

foo = function (a) ifelse(a,a,b)
Run Code Online (Sandbox Code Playgroud)

现在foo适用于任何长度a.事实上,它甚至可以在a矩阵时使用.返回一个与形状相同的值,test这个特征有助于矢量化,而不是问题.

  • 在这种情况下,使用“ifelse(a,a,b)”是我最讨厌的。看起来 `?ifelse` 中的每一行都在尖叫,“不要用我代替 `if (a) {a} else b`。” 例如“...返回一个与`test`形状相同的值”,“如果`yes`或`no`太短,它们的元素将被回收。”,“结果的模式可能取决于`test`", "结果的类属性取自`test`,可能不适合从`yes`和`no`中选择的值" (3认同)

Hug*_*ins 13

没有在结尾显式地放置'return'的问题是如果在方法结束时添加了额外的语句,突然返回值是错误的:

foo <- function() {
    dosomething()
}
Run Code Online (Sandbox Code Playgroud)

这将返回值dosomething().

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

foo <- function() {
    dosomething()
    dosomething2()
}
Run Code Online (Sandbox Code Playgroud)

我们希望我们的代码返回值dosomething(),但不再如此.

通过明确的回报,这变得非常明显:

foo <- function() {
    return( dosomething() )
    dosomething2()
}
Run Code Online (Sandbox Code Playgroud)

我们可以看到这段代码有些奇怪,并修复它:

foo <- function() {
    dosomething2()
    return( dosomething() )
}
Run Code Online (Sandbox Code Playgroud)

  • @HughPerkins不是*没有真正的苏格兰人*;相反,它是关于代码复杂性的经验性观察,并得到数十年软件工程最佳实践的支持:保持单个函数简短,代码流显而易见。省略return不是捷径,而是函数式编程中的“适当”风格。使用不必要的“返回”函数调用是[cargo cult编程](https://en.wikipedia.org/wiki/Cargo_cult_programming)的一个实例。 (2认同)

Kon*_*lph 13

我的问题是:为什么不打电话return更快

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

也就是说,以这种方式调用原始函数在 R 中非常快,并且调用return会产生极小的开销。这不是省略return.

或更好,因此更可取?

因为没有任何理由使用它。

因为它是多余的,并且没有添加有用的冗余。

需要明确的是:冗余有时是有用的。但大多数冗余不是这种类型。相反,它是一种在不添加信息的情况下增加视觉混乱的类型:它是填充词图表垃圾的编程等价)。

考虑以下解释性注释的示例,它被普遍认为是错误的冗余,因为该注释只是解释了代码已经表达的内容:

# Add one to the result
result = x + 1
Run Code Online (Sandbox Code Playgroud)

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

一个这样的积极原因是发出提前退出函数的信号,例如在保护子句中

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

这是对 的有效、非冗余使用return。但是,与其他语言相比,这种保护子句在 R 中很少见,并且由于每个表达式都有一个值,因此正if则不需要return

sign = function (num) {
    if (num > 0) {
        1
    } else if (num < 0) {
        -1
    } else {
        0
    }
}
Run Code Online (Sandbox Code Playgroud)

我们甚至可以这样重写f

f = function (a, b) {
    if (precondition(a)) calculation(b)
}
Run Code Online (Sandbox Code Playgroud)

...if (cond) exprif (cond) expr else NULL.相同的地方。

最后,我想避免三种常见的反对意见:

  1. 有些人认为 usingreturn增加了清晰度,因为它表示“此函数返回一个值”。但如上所述,R 中的每个函数都会返回一些内容。将其return视为返回值的标记不仅是多余的,而且是一种积极的误导

  2. 相关地,Python有一个奇妙的指导方针,应该始终遵循:

    显式优于隐式。

    删除冗余如何不return违反这一点?因为函数式语言中函数的返回值总是显式的:它是它的最后一个表达式。这又是关于显性冗余的相同论点。

    事实上,如果您想要明确性,请使用它来突出规则的例外:标记返回有意义值的函数,这些函数仅因其副作用(例如cat)而被调用。除了 R 有一个比return这种情况更好的标记:invisible。例如,我会写

    save_results = function (results, file) {
        # … code that writes the results to a file …
        invisible()
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 但是长函数呢?忘记返回的内容会不会很容易?

    两个答案:首先,不是真的。规则很明确:函数的最后一个表达式是它的值。没有什么可跟踪的。

    但更重要的是,长函数的问题不在于缺乏明确的return标记。这是函数长度。长函数几乎 (?) 总是违反单一职责原则,即使他们不这样做,他们也会从可读性拆分中受益。

  • 我来到这个问题时认为使用“return”支持明显更好,并以充分的批评阅读您的答案。你的回答让我反思了这个观点。我认为显式使用“return”的需要(至少在我自己的情况下)与需要能够在以后的时间点更好地修改我的函数有关。考虑到我的函数可能太复杂了,我现在可以看到,改进我的编程风格的目标是努力构建代码以在没有“返回”的情况下保持显式性。感谢您的反思和见解! (2认同)

leb*_*nok 5

我认为这return是一个技巧.作为一般规则,函数中计算的最后一个表达式的值将成为函数的值 - 并且在许多地方都可以找到此常规模式.以下所有评估为3:

local({
1
2
3
})

eval(expression({
1
2
3
}))

(function() {
1
2
3
})()
Run Code Online (Sandbox Code Playgroud)

什么return不是真正返回一个值(无论是否有它),但以不规则的方式"突破"函数.从这个意义上说,它是R中最接近的GOTO语句(也有中断和下一个).我return很少使用,也从不使用函数.

 if(a) {
   return(a)
 } else {
   return(b)
 }
Run Code Online (Sandbox Code Playgroud)

...这可以重写,因为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")
   }
Run Code Online (Sandbox Code Playgroud)

一般来说,需要许多回报表明问题要么是丑陋的,要么结构不合理

<>

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)
Run Code Online (Sandbox Code Playgroud)

  • 这是因为您使用了“|”(向量化 OR,其中两边都被求值)而不是“||”(短路 OR,非向量化,其中谓词依次求值)。考虑“if (TRUE || stop()) print(1)”与“if (TRUE || stop()) print(1)” (2认同)