43

Hadley Wickham最近在r-devel 邮件列表上提出了一个有趣的问题,并且无法在 StackOverflow 上找到有关该主题的现有问题,我认为它也存在于此处可能很有用。

转述:

R 函数由三个元素组成:参数列表、主体和环境。我们可以从这三个元素以编程方式构造一个函数吗?

(在上面 r-devel 链接中的线程末尾有一个相当全面的答案。我将把它留给其他人自己重新创建各种解决方案的基准测试并将其作为答案提供,但一定要引用 Hadley如果你这样做。如果几个小时内没有人站出来,我会自己做。)

4

4 回答 4

52

这是对这里讨论的扩展。

我们的三个部分需要是一个参数列表、一个主体和一个环境。

对于环境,我们将简单地env = parent.frame()默认使用。

我们并不真正想要一个常规的旧列表作为参数,所以我们使用alist 它有一些不同的行为:

“......不评估值,并且允许没有值的标记参数”

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

对于body,我们quote的表达式得到一个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
于 2012-10-19T23:56:22.297 回答
7

还有一个以alist编程方式创建对象的问题,因为当参数的数量是可变的时,这对于创建函数很有用。

Analist只是一个空符号的命名列表。这些空符号可以用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
于 2014-10-09T14:17:34.530 回答
7

rlang有一个名为的函数new_function

用法

new_function(args, body, env = caller_env())

library(rlang)
g <- new_function(alist(x = ), quote(x + 3))
g
# function (x) 
# x + 3
于 2018-09-06T22:30:51.420 回答
1

我不确定这会有所帮助,但下面的代码在某些情况下可能会有所帮助,

hello_world可以是用于创建函数的字符串,assign 将用于命名函数hello_world

hello_world <- "print('Hello World')"

assign("Hello",function()
  {
    eval(parse(text = hello_world))
  }, envir = .GlobalEnv)

This will create a function called hello_world

于 2019-10-26T12:56:19.570 回答