Явное вызов return в функции или нет
Некоторое время назад меня упрекнул Саймон Урбанек из основной группы R (я считаю) за рекомендацию пользователю явно вызвать return
в конце функции (его комментарий был удален, хотя):
foo = function() { return(value) }
вместо этого он рекомендовал:
foo = function() { value }
Вероятно, в такой ситуации требуется:
- Как вы конвертируете даты / время из одного часового пояса в другой в R?
- R: t-тест по всем столбцам
- Свернуть текст по группе в кадре данных
- Ошибка: использование стека C слишком близко к пределу
- Открытие всех файлов в папке и применение функции
foo = function() { if(a) { return(a) } else { return(b) } }
Его комментарий пролил некоторый свет на то, почему не называть return
если строго необходимо, это хорошо, но это было удалено.
Мой вопрос: почему не называть return
быстрее или лучше, и, следовательно, предпочтительнее?
- Что делает функция invisible ()?
- Удалите повторяющиеся пары столбцов, сортируйте строки на основе двух столбцов
- Процент% в%
- Перечислить списки в R
- Поиск локальных максимумов и минимумов
- Как определить, есть ли у вас интернет-соединение в R
- найти все функции (включая частные) в пакете
- Последовательные / подвижные суммы в векторе в R
Вопрос: почему нет (явно) вызов возврата быстрее или лучше и, следовательно, предпочтительнее?
В документации 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 >
Это зависит от страtagsи и стиля программирования программиста, какой стиль он использует, он не может использовать return (), поскольку он не требуется.
R основных программистов использует оба подхода, т.е. с явным возвратом () и без него, как можно найти в источниках «базовых» функций.
Многократно используется только return () (без аргумента), возвращающий NULL в случаях, чтобы условно остановить функцию.
Неясно, лучше ли это или нет, поскольку стандартный пользователь или аналитик, использующий R, не может видеть реальную разницу.
Мое мнение таково, что вопрос должен быть: есть ли какая-либо опасность при использовании явного возвращения из реализации R?
Или, может быть, лучше, код функции написания пользователем всегда должен спрашивать: каков эффект от использования явного возврата (или размещения объекта, который будет возвращен как последний лист ветви кода) в коде функции?
Если все согласятся с тем, что
-
return
не требуется в конце тела функции - не используя
return
немного быстрее (согласно тесту @ Алана, 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
s и сложная система булевых переменных. Все это говорит о том, что правило единственного возврата не очень хорошо работает с R. Итак, если вам нужно будет использовать return
в некоторых местах тела вашей функции, почему бы не быть последовательным и использовать его повсюду?
Я не думаю, что аргумент скорости является допустимым. Разница в 0,8 микросекунды - это ничто, когда вы начинаете смотреть на функции, которые на самом деле что-то делают. Последнее, что я вижу, это то, что он набирает меньше, но эй, я не ленив.
Кажется, что без 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)
к другим критериям ( benchmark(fuu(x),foo(x),replications=1e7)
), и результат отменяется ... Я постараюсь на сервере.
Это интересная дискуссия. Я думаю, что пример @ 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
- matrix. Возrotation значения той же формы, что и test
является функцией, которая помогает с векторизации, а не проблемой.
Проблема, заключающаяся в том, что я не добавляю «return» явно в конце, состоит в том, что если добавить конец в конец метода, то внезапное возвращаемое значение неверно:
foo <- function() { dosomething() }
Это возвращает значение dosomething()
.
Теперь мы приходим на следующий день и добавляем новую строку:
foo <- function() { dosomething() dosomething2() }
Мы хотели, чтобы наш код возвращал значение dosomething()
, но вместо этого он больше не работает.
С явным возвратом это становится действительно очевидным:
foo <- function() { return( dosomething() ) dosomething2() }
Мы можем видеть, что в этом коде есть что-то странное и исправить:
foo <- function() { dosomething2() return( dosomething() ) }
Я думаю о 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)
return
может повысить читаемость кода:
foo <- function() { if (a) return(a) b }