Явное вызов 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     
}