Hadley Wickham recently asked an interesting question on the r-devel mailing list, and being unable to find an existing question on the topic on StackOverflow, I thought it might be useful for it exist here as well.
To paraphrase:
An R function consists of three elements: an argument list, a body and an environment. Can we construct a function programmatically from these three elements?
(A fairly comprehensive answer is reached at the end of the thread in the r-devel link above. I will leave this open for others to recreate the benchmarking of the various solutions themselves and supply it as an answer, but be sure to cite Hadley if you do. If no one steps up in a few hours I'll do it myself.)
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
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
I am not sure this will help, but below code might be beneficial in some scenarios,
hello_world can be the string which will be used to create function and assign will be used to name function 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
Related
I'm trying to use the curve3d function in the emdbook-package to create a contour plot of a function defined locally inside another function as shown in the following minimal example:
library(emdbook)
testcurve3d <- function(a) {
fn <- function(x,y) {
x*y*a
}
curve3d(fn(x,y))
}
Unexpectedly, this generates the error
> testcurve3d(2)
Error in fn(x, y) : could not find function "fn"
whereas the same idea works fine with the more basic curve function of the base-package:
testcurve <- function(a) {
fn <- function(x) {
x*a
}
curve(a*x)
}
testcurve(2)
The question is how curve3d can be rewritten such that it behaves as expected.
You can temporarily attach the function environment to the search path to get it to work:
testcurve3d <- function(a) {
fn <- function(x,y) {
x*y*a
}
e <- environment()
attach(e)
curve3d(fn(x,y))
detach(e)
}
Analysis
The problem comes from this line in curve3d:
eval(expr, envir = env, enclos = parent.frame(2))
At this point, we appear to be 10 frames deep, and fn is defined in parent.frame(8). So you can edit the line in curve3d to use that, but I'm not sure how robust this is. Perhaps parent.frame(sys.nframe()-2) might be more robust, but as ?sys.parent warns there can be some strange things going on:
Strictly, sys.parent and parent.frame refer to the context of the
parent interpreted function. So internal functions (which may or may
not set contexts and so may or may not appear on the call stack) may
not be counted, and S3 methods can also do surprising things.
Beware of the effect of lazy evaluation: these two functions look at
the call stack at the time they are evaluated, not at the time they
are called. Passing calls to them as function arguments is unlikely to
be a good idea.
The eval - parse solution bypasses some worries about variable scope. This passes the value of both the variable and function directly as opposed to passing the variable or function names.
library(emdbook)
testcurve3d <- function(a) {
fn <- eval(parse(text = paste0(
"function(x, y) {",
"x*y*", a,
"}"
)))
eval(parse(text = paste0(
"curve3d(", deparse(fn)[3], ")"
)))
}
testcurve3d(2)
I have found other solution that I do not like very much, but maybe it will help you.
You can create the function fn how a call object and eval this in curve3d:
fn <- quote((function(x, y) {x*y*a})(x, y))
eval(call("curve3d", fn))
Inside of the other function, the continuous problem exists, a must be in the global environment, but it is can fix with substitute.
Example:
testcurve3d <- function(a) {
fn <- substitute((function(x, y) {
c <- cos(a*pi*x)
s <- sin(a*pi*y/3)
return(c + s)
})(x, y), list(a = a))
eval(call("curve3d", fn, zlab = "fn"))
}
par(mfrow = c(1, 2))
testcurve3d(2)
testcurve3d(5)
The documentation of do.call states:
If quote is FALSE, the default, then the arguments are evaluated (in the calling environment, not in envir).
This sentence would suggest to me that when quote = FALSE, specifying envir makes no difference. However, that's not the case, and in fact I've encountered cases where I need to specify envir to get the function to work.
Simplest reproducible example:
g1 <- function(x) {
args <- as.list(match.call())
args[[1]] <- NULL # remove the function call
do.call(print, args, quote = FALSE) # call print()
}
g2 <- function(x) {
args <- as.list(match.call())
args[[1]] <- NULL # remove the function call
do.call(print, args, quote = FALSE, envir = parent.frame()) # call print(), specifying envir
}
h1 <- function(x, y) {
g1(x*y)
}
h2 <- function(x, y) {
g2(x*y)
}
With these functions, h2() behaves as one would think but h1() does not:
h1(2, 3)
#Error in print(x) : object 'y' not found
h2(2, 3)
#[1] 6
y <- 100
h1(2, 3)
#[1] 600
## Looks like g1() took the value of y from the global environment
h2(2, 3)
#[1] 6
Can anybody explain to me what's going on here?
Note: There's a related post here but by my reading the answers don't specifically state what do.call() does with the envir variable.
?do.call says:
envir
an environment within which to evaluate the call. This will be
most useful if what is a character string and the arguments are
symbols or quoted expressions.
We can easily illustrate this if the what= argument of do.call is a character string. Then envir= determines where it is looked up.
e <- new.env()
e$f <- function() 2
f <- function() 3
do.call("f", list())
## [1] 3
do.call("f", list(), envir = e)
## [1] 2
The same is true for the arguments as the code in the question shows. Note that the arguments are already quoted since match.call() is being used.
What is happening in the case of h1 and g1 is that this is effectively run within g1
do.call(print, list(call("*", quote(x), quote(y))), quote = FALSE)
Now it finds x in g1 (since g1 has one argument x) but there is no y in g1 so it looks to the parent environment of g1 which is the global environment where it finds y.
In the case of h2 and g2 it runs this in g2:
do.call(print, list(call("*", quote(x), quote(y))), quote = FALSE, envir = parent.frame())
and it finds x and y in h2 which is the parentframe of g2.
Note that the parent environment is not the same as the parent frame:
the parent environment is determined by where the function was defined so if the function was defined in the global environment then its parent environment is the global environment.
the parent frame is the environment of the caller
Hadley Wickham recently asked an interesting question on the r-devel mailing list, and being unable to find an existing question on the topic on StackOverflow, I thought it might be useful for it exist here as well.
To paraphrase:
An R function consists of three elements: an argument list, a body and an environment. Can we construct a function programmatically from these three elements?
(A fairly comprehensive answer is reached at the end of the thread in the r-devel link above. I will leave this open for others to recreate the benchmarking of the various solutions themselves and supply it as an answer, but be sure to cite Hadley if you do. If no one steps up in a few hours I'll do it myself.)
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
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
I am not sure this will help, but below code might be beneficial in some scenarios,
hello_world can be the string which will be used to create function and assign will be used to name function 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
I tried implementing a function let with the following semantics:
> let(x = 1, y = 2, x + y)
[1] 3
… which is conceptually somewhat similar to substitute with the syntax of with.
The following code almost works (the above invocation for instance works):
let <- function (...) {
args <- match.call(expand.dots = FALSE)$`...`
expr <- args[[length(args)]]
eval(expr,
list2env(lapply(args[-length(args)], eval), parent = parent.frame()))
}
Note the nested eval, the outer to evaluate the actual expression and the inner to evaluate the arguments.
Unfortunately, the latter evaluation happens in the wrong context. This becomes apparent when trying to call let with a function that examines the current frame, such as match.call:
> (function () let(x = match.call(), x))()
Error in match.call() :
unable to find a closure from within which 'match.call' was called
I thought of supplying the parent frame as the evaluating environment for eval, but that doesn’t work:
let <- function (...) {
args <- match.call(expand.dots = FALSE)$`...`
expr <- args[[length(args)]]
parent <- parent.frame()
eval(expr,
list2env(lapply(args[-length(args)], function(x) eval(x, parent)),
parent = parent)
}
This yields the same error. Which leads me to the question: how exactly is match.call evaluated? Why doesn’t this work? And, how do I make this work?
Will this rewrite solve your problem?
let <- function (expr, ...) {
expr <- match.call(expand.dots = FALSE)$expr
given <- list(...)
eval(expr, list2env(given, parent = parent.frame()))
}
let(x = 1, y = 2, x + y)
# [1] 3
Hadley Wickham recently asked an interesting question on the r-devel mailing list, and being unable to find an existing question on the topic on StackOverflow, I thought it might be useful for it exist here as well.
To paraphrase:
An R function consists of three elements: an argument list, a body and an environment. Can we construct a function programmatically from these three elements?
(A fairly comprehensive answer is reached at the end of the thread in the r-devel link above. I will leave this open for others to recreate the benchmarking of the various solutions themselves and supply it as an answer, but be sure to cite Hadley if you do. If no one steps up in a few hours I'll do it myself.)
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
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
I am not sure this will help, but below code might be beneficial in some scenarios,
hello_world can be the string which will be used to create function and assign will be used to name function 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