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