在R中捕获表达式作为函数体

时间:2013-05-11 09:33:50

标签: r metaprogramming eval substitution

我正在尝试编写一个程序,它将表达式作为输入,并返回一个函数,该表达式绑定为其主体。

caller <- function (expr, params) {

    Function <- function (params, body, env = parent.frame()) {
        # returns a function

    }

    Function(params, body = expr)
}

func <- caller (a + b, c('a', 'b'))

func(1, 2)
[1] 3

我可以通过使用类似

之类的东西轻松绑定参数
params <- c('a', 'b')
f <- function() {} 
formals(f) <- structure(
    replicate(length(params), NULL),
    names = params
)

我无法想出一种动态添加表达式作为正文的方法。我已经尝试过使用substitute(),并从pryr库中调整make_function,但是我无法完成任务。我最好的尝试是

    body(f, parent.frame()) <- as.list( match.call() )[-1]$body

我也无法使用替代品。关于如何绑定主体以使最顶层的程序按预期工作的任何想法?

我在SO上看过similar questions,但解决方案似乎并不能解决这个问题。

3 个答案:

答案 0 :(得分:4)

简单地说:

caller <- function(expr, params) {
  f <- function() NULL
  formals(f) <- structure(replicate(length(params), NULL), names=params)
  body(f, envir=parent.frame()) <- substitute(expr)
  f
}

它不使用内部函数,这可能导致substitute的问题。

请注意,我不确定这是否按照您想要的方式设置返回函数的环境。这会将其设置为您调用caller的环境。

答案 1 :(得分:3)

这是一个允许参数没有默认值的解决方案。传递参数名称也更容易,因为它们不必用引号括起来。

请检查以下代码中的评论:

g <- function(...)
{
    # Get the arguments as unevaluated expressions:

    L <- as.list(substitute(list(...)))[-1]

    # The first argument is the body expression (technically a call object):

    expr <- L[[1]]

    # If the expression is not enclosed in curly braces, let's force it:

    if( as.character(expr[[1]]) != "{" ) expr <- call("{", expr)

    # Drop the first argument:

    L <- L[-1]

    # Mark symbols to be used as names for missing parameters:

    filter <- vapply(L, is.symbol, logical(1))

    params <- L

    # The obscure expression "formals(function(x){})$x" returns a missing value, something really arcane ;-) :

    params[filter] <- list(formals(function(x){})$x)

    # Here the symbols are used as names:

    names(params)[filter] <- vapply(L[filter], as.character, character(1))

    # Now the result:

    f <- function(){}

    formals(f) <- params

    body(f) <- expr

    # Just to make it nicier, let's define the enclosing environment as if the function were created outside g:

    environment(f) <- parent.frame()

    f
}

一些测试:

> g(a+b, a, b=1)
function (a, b = 1) 
{
    a + b
}


> f <- g({x <- a+b; x^2}, a, b)
> f
function (a, b) 
{
    x <- a + b
    x^2
}
> f(2,3)
[1] 25
> f(1)
Error in a + b : 'b' is missing

> g(a+b, a=2, b=2)()
[1] 4

答案 2 :(得分:0)

指定函数参数的一种有趣的替代方法是使用与alist函数相同的机制,该函数通常与formals一起使用。这就是它在base包中的定义:

alist <- function (...) as.list(sys.call())[-1L]

这很容易适用于caller

caller <- function(...) {
  f <- function() NULL
  formals(f) <- as.list(sys.call())[-(1:2)]
  body(f, envir=parent.frame()) <- substitute(list(...))[[2]]
  f
}

第一个参数仍指定函数体,其余参数与alist完全相同。

> func <- caller(a+b, a=, b=10)
> func(1)
[1] 11
> func <- caller(a+b, a=, b=a)
> func(10)
[1] 20

您甚至可以创建使用...的函数:

> func <- caller(c(...), ...=)
> func("a", "b", "c")
[1] "a" "b" "c"