Подтвердить что ты не робот

Как программно создать функцию R?

Hadley Wickham недавно задал интересный вопрос о r-devel mailing список, и, будучи неспособным найти существующий вопрос по теме в StackOverflow, я подумал, что это может быть полезно для него и здесь.

Перефразировать:

Функция R состоит из трех элементов: списка аргументов, тела и среды. Можно ли программно построить функцию из этих трех элементов?

(Достаточно полный ответ достигается в конце потока в ссылке r-devel выше. Я оставлю это открытым для других, чтобы воссоздать бенчмаркинг различных решений и предоставить его в качестве ответа, но обязательно процитировать Хэдли, если вы это сделаете. Если никто не встанет через несколько часов, я сделаю это сам.)

4b9b3361

Ответ 1

Это расширение на обсуждение здесь.

Наши три части должны быть списком аргументов, телом и средой.

Для среды мы просто используем env = parent.frame() по умолчанию.

Нам не нужен обычный старый список аргументов, поэтому вместо этого мы используем alist который имеет некоторое другое поведение:

"... значения не оцениваются, а помеченные аргументы без значения допускаются"

args <- alist(a = 1, b = 2)

Для тела we quote наше выражение получим a call:

body <- quote(a + b)

Один из вариантов состоит в том, чтобы преобразовать args в список пар, а затем просто вызвать функцию function используя eval:

make_function1 <- function(args, body, env = parent.frame()) {
      args <- as.pairlist(args)
      eval(call("function", args, body), env)
}

Другой вариант - создать пустую функцию, а затем заполнить ее необходимыми значениями:

make_function2 <- function(args, body, env = parent.frame()) {
      f <- function() {}
      formals(f) <- args
      body(f) <- body
      environment(f) <- env

      f
}

Третий вариант - просто использовать as.function:

make_function3 <- function(args, body, env = parent.frame()) {
      as.function(c(args, body), env)
}

И, наконец, это похоже на первый метод для меня, кроме мы используем несколько другую идиому для создания вызова функции, используя substitute, а не call:

make_function4 <- function(args, body, env = parent.frame()) {
      subs <- list(args = as.pairlist(args), body = body)
      eval(substitute(`function`(args, body), subs), env)
}


library(microbenchmark)
microbenchmark(
      make_function1(args, body),
      make_function2(args, body),
      make_function3(args, body),
      make_function4(args, body),
      function(a = 1, b = 2) a + b
    )

Unit: nanoseconds
                          expr   min      lq  median      uq    max
1 function(a = 1, b = 2) a + b   187   273.5   309.0   363.0    673
2   make_function1(args, body)  4123  4729.5  5236.0  5864.0  13449
3   make_function2(args, body) 50695 52296.0 53423.0 54782.5 147062
4   make_function3(args, body)  8427  8992.0  9618.5  9957.0  14857
5   make_function4(args, body)  5339  6089.5  6867.5  7301.5  55137

Ответ 2

Существует также проблема создания объектов alist программно, поскольку это может быть полезно для создания функций, когда число аргументов является переменной.

An alist - это просто именованный список пустых символов. Эти пустые символы могут быть созданы с помощью substitute(). Итак:

make_alist <- function(args) {
  res <- replicate(length(args), substitute())
  names(res) <- args
  res
}

identical(make_alist(letters[1:2]), alist(a=, b=))
## [1] TRUE