Явное вызов return в функции или нет
A в то время как Я упрекнул Саймона Урбанека из основной группы R (я считаю) за то, что он рекомендовал пользователю явно называть return
в конце (его комментарий был удален, хотя):
foo = function() {
return(value)
}
вместо этого он рекомендовал:
foo = function() {
value
}
Вероятно, в такой ситуации требуется:
foo = function() {
if(a) {
return(a)
} else {
return(b)
}
}
В его комментарии прослезился вопрос о том, почему не вызывать return
, если только это не нужно, это хорошая вещь, но это было удалено.
Мой вопрос: почему не звонит return
быстрее или лучше, и, следовательно, предпочтительнее?
Ответы
Ответ 1
Вопрос: почему (явно) вызов не возвращается быстрее или лучше и, следовательно, предпочтительнее?
В документации по R нет такого утверждения.
Эта страница man? '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
![Function elapsed time comparison]()
Рисунок выше может немного отличаться на вашей платформе.
Исходя из измеренных данных, размер возвращаемого объекта не вызывает никакой разницы, количество повторов (даже при увеличении) делает только очень небольшую разницу, которая в реальном слове с реальными данными и реальным алгоритмом не может быть подсчитана или сделать script выполняется быстрее.
Лучше ли это без вызова возврата?
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() (без аргумента), возвращающий NULL в случаях, чтобы условно остановить функцию.
Неясно, лучше ли это или нет, поскольку обычный пользователь или аналитик, использующий R, не видит реальной разницы.
Мое мнение заключается в том, что вопрос должен быть: существует ли какая-либо опасность при использовании явного возвращения из реализации R?
Или, может быть, лучше, пользовательский код функции записи всегда должен спрашивать: каков эффект в не, используя явный возврат (или помещение объекта для возврата в качестве последнего листа ветки кода) в код функции
Ответ 2
Если все согласны с тем, что
-
return
не требуется в конце тела функции
- не используется
return
немного быстрее (в соответствии с тестом @Alan, 4.3 микросекунды против 5.1)
мы все перестанем использовать return
в конце функции? Я, конечно, не буду, и я хотел бы объяснить, почему. Я надеюсь услышать, что другие люди разделяют мое мнение. И я извиняюсь, если это не прямой ответ OP, а скорее длинный субъективный комментарий.
Моя основная проблема с использованием return
заключается в том, что, как заметил Павел, в теле функции есть другие места, где вам может понадобиться. И если вы вынуждены использовать return
где-то в середине вашей функции, почему бы не сделать все инструкции return
явным? Я ненавижу быть непоследовательным. Также я думаю, что код читается лучше; можно сканировать функцию и легко видеть все точки выхода и значения.
Павел использовал этот пример:
foo = function() {
if(a) {
return(a)
} else {
return(b)
}
}
К сожалению, можно отметить, что его можно легко переписать как:
foo = function() {
if(a) {
output <- a
} else {
output <- b
}
output
}
Последняя версия даже соответствует некоторым стандартам программирования, которые защищают один оператор возврата за каждую функцию. Я думаю, что лучший пример мог бы быть:
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
и сложная система булевых переменных. Все это говорит о том, что правило единственного возврата не очень хорошо работает с R. Итак, если вам нужно использовать return
в некоторых местах вашего тела функции, почему бы не быть последовательным и использовать его везде?
Я не думаю, что аргумент скорости является допустимым. Разница в 0,8 микросекунды - это ничто, когда вы начинаете смотреть на функции, которые на самом деле что-то делают. Последнее, что я вижу, это то, что он набирает меньше, но эй, я не ленив.
Ответ 3
Кажется, что без 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 __________________
Я перехожу к другим критериям (benchmark(fuu(x),foo(x),replications=1e7)
), и результат отменяется... Я постараюсь на сервере.
Ответ 4
Это интересная дискуссия. Я думаю, что пример @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
, является функцией, которая помогает с векторизации, а не проблемой.
Ответ 5
Проблема с тем, что я не помещаю "return" явно в конце, заключается в том, что если добавить конец в конец метода, то внезапное возвращаемое значение неверно:
foo <- function() {
dosomething()
}
Это возвращает значение dosomething()
.
Теперь мы подошли к следующему дню и добавим новую строку:
foo <- function() {
dosomething()
dosomething2()
}
Мы хотели, чтобы наш код возвращал значение dosomething()
, но вместо этого он больше не делает.
С явным возвратом это становится действительно очевидным:
foo <- function() {
return( dosomething() )
dosomething2()
}
Мы можем видеть, что в этом коде есть что-то странное и исправить:
foo <- function() {
dosomething2()
return( dosomething() )
}
Ответ 6
Я думаю о return
как об уловке. Как правило, значение последнего выражения, оцениваемого функцией, становится значением функции - и этот общий шаблон встречается во многих местах. Все следующие оценки равны 3:
local({
1
2
3
})
eval(expression({
1
2
3
}))
(function() {
1
2
3
})()
Что return
действительно не возвращает значение (это делается с ним или без него), но "вырывание" функции нерегулярно. В этом смысле это самый близкий эквивалент оператора GOTO в R (также есть разрыв и следующий). Я использую 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")
}
Как правило, необходимость в многократном возвращении предполагает, что проблема является либо уродливой, либо плохо структурированной.
< >
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)
Ответ 7
return
может повысить читаемость кода:
foo <- function() {
if (a) return(a)
b
}