Каковы преимущества определения и вызова функции внутри другой функции в R?

Подход 1

f1 <- function(x)
{
    # Do calculation xyz ....

    f2 <- function(y)
    {
        # Do stuff...
        return(some_object)
    }

    return(f2(x))
}

Подход 2

f2 <- function(y)
{
    # Do stuff...

    return(some_object)
}

f3 <- function(x)
{
    # Do calculation xyz ....
    return(f2(x))
}

Предположим, что f1 и f3 выполняют одинаковые вычисления и дают тот же результат.

Есть ли существенные преимущества в использовании подхода 1, вызывающего f1(), vs подход 2, вызывающий f3()?

Является ли определенный подход более благоприятным, если:

  • большие данные передаются и/или из f2?

  • Скорость - большая проблема. Например. f1 или f3 неоднократно вызываются в симуляциях.

(Подход 1 кажется общим в пакетах, определяющим внутри другого)

Одно из преимуществ использования подхода f1 заключается в том, что f2 не будет существовать вне f1 после того, как будет завершен вызов f1f2 вызывается только в f1 или f3).

Ответ 1

Преимущества определения f2 внутри f1:

  • f2 отображается только в f1, полезно, если f2 предназначен только для использования внутри f1, хотя в пространствах имен пакетов это дискуссионно, поскольку вы просто не экспортируете f2, если вы определили его вне
  • f2 имеет доступ к переменным в f1, что можно считать хорошим или плохим:
    • хорошо, потому что вам не нужно передавать переменные через функциональный интерфейс, и вы можете использовать <<- для реализации таких вещей, как memoization и т.д.
    • Плохо, по тем же причинам...

Недостатки:

  • f2 необходимо переопределять каждый раз, когда вы вызываете f1, что добавляет некоторые накладные расходы (не очень много накладных расходов, но определенно там)

Размер данных не должен иметь значения, так как R не будет копировать данные, если он не изменяется в любом сценарии. Как отмечалось в недостатках, определение f2 вне f1 должно быть немного быстрее, особенно если вы повторяете относительно низкую служебную операцию много раз. Вот пример:

> fun1 <- function(x) {
+   fun2 <- function(x) x
+   fun2(x)
+ }
> fun2a <- function(x) x
> fun3 <- function(x) fun2a(x)
> 
> library(microbenchmark)
> microbenchmark(
+   fun1(TRUE), fun3(TRUE)
+ )
Unit: nanoseconds
       expr min    lq median    uq   max neval
 fun1(TRUE) 656 674.5  728.5 859.5 17394   100
 fun3(TRUE) 406 434.5  480.5 563.5  1855   100

В этом случае мы сохраняем 250ns (редактируем: разница на самом деле равна 200ns; верьте, что это или нет дополнительный набор {}, который fun1 стоит еще 50 нс). Не так много, но может складываться, если внутренняя функция сложнее или вы повторяете функцию много раз.

Ответ 2

Обычно вы используете подход 2. Некоторые исключения:

  • Закрытие функций:

    f = function() {
        counter = 1
        g = function() {
            counter <<- counter + 1
            return(counter)
        }
     }
     counter = f()
     counter()
     counter()
    

    Закрытие функции позволяет нам запомнить состояние.

  • Иногда это удобно только для определения функций, поскольку они используются только в одном месте. Например, при использовании optim мы часто настраиваем существующую функцию. Например,

    pdf = function(x, mu) dnorm(x, mu, log=TRUE)
    f = function(d, lower, initial=0) {
      ll = function(mu) {
        if(mu < lower) return(-Inf)
        else -sum(pdf(d, mu))
      }
      optim(initial, ll)
    }
    
    f(d, 1.5)
    

    Функция ll использует набор данных d и нижнюю границу. Это удобно, так как это может быть единственный раз, когда мы используем/нуждаемся в функции ll.