How to create an R function programmatically?

rlang has a function called new_function that does this :

Usage

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

library(rlang)
g <- new_function(alist(x = ), quote(x + 3))
g
# function (x) 
# x + 3

There is also the issue of creating alist objects programmatically as that can be useful for creating functions when the number of arguments is variable.

An alist is simply a named list of empty symbols. These empty symbols can be created with substitute(). So:

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

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

This is an expansion on the discussion here.

Our three pieces need to be an argument list, a body and an environment.

For the environment, we will simply use env = parent.frame() by default.

We do not really want a regular old list for the arguments, so instead we use alist which has some different behavior:

"...values are not evaluated, and tagged arguments with no value are allowed"

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

For the body, we quote our expression to get a call:

body <- quote(a + b)

One option is to convert args to a pairlist and then simply call the function function using eval:

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

Another option is to create an empty function, and then fill it with the desired values:

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

      f
}

A third option is to simply use as.function:

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

And finally, this seems very similar to the first method to me, except we are using a somewhat different idiom to create the function call, using substitute rather than 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

Tags:

Function

R