capturing an expression as a function body in R

I'm trying to write a program that takes an expression as an input and returns a function with that expression bound as its body.

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

I can bind the parameters quite easily, by using something like

params <- c('a', 'b')
f <- function() {} 
formals(f) <- structure(
    replicate(length(params), NULL),
    names = params
)

I'm having trouble coming up with a way of dynamically adding the expression as the body. I've tried use substitute(), and adapting make_function from the pryr library, but I can't quite get things to work. My best attempt is

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

I couldn't get this to work with substitute either. Any thoughts on how to bind the body in so that the topmost program works as expected?

I've seen similar questions on SO, but the solutions don't seem to satistfy this problem.

Upvotes: 7

Views: 827

Answers (3)

Steve Weston
Steve Weston

Reputation: 19677

An interesting alternative way of specifying the function arguments is to use the same mechanism as the alist function, which is commonly used in conjunction with formals. This is how it's defined in the base package:

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

This is easily adapted to work with caller:

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

The first argument still specifies the function body, and the remaining arguments work exactly as in alist.

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

You can even create functions that use ...:

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

Upvotes: 0

Ferdinand.kraft
Ferdinand.kraft

Reputation: 12819

Here is a solution to allow parameters with no default value. It's also easier to pass parameters names, as they don't have to be enclosed with quotes.

Please check the comments in the code below:

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
}

Some tests:

> 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

Upvotes: 4

Steve Weston
Steve Weston

Reputation: 19677

How about simply:

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

It doesn't use an inner function, which may have been causing your problems with substitute.

Note that I'm not certain if this is setting the environment of the returned function the way that you want. This sets it to the environment from which you call caller.

Upvotes: 4

Related Questions