When running a function in R, I run another function within it. I have a code on the lines of this:
f_a <- function(b, c){
return(b + c)
}
f_e <- function(){
b = 2
c = 2
d = f_a(b, c)
print(d)
}
This works fine. What I'd like to do is not pass the variables b, c
to the function f_a
. I'd like to do something like this (which throws errors)
f_a <- function(){
return(b + c)
}
f_e <- function(){
b = 2
c = 2
d = f_a()
print(d)
}
Is there a way to do this using environments or search paths or any other way?
I do encourage you to read about lexical scoping, but I think a good approach to avoid writing a lot of variables could be:
get_args_for <- function(fun, env = parent.frame(), inherits = FALSE, ..., dots) {
potential <- names(formals(fun))
if ("..." %in% potential) {
if (missing(dots)) {
# return everything from parent frame
return(as.list(env))
}
else if (!is.list(dots)) {
stop("If provided, 'dots' should be a list.")
}
potential <- setdiff(potential, "...")
}
# get all formal arguments that can be found in parent frame
args <- mget(potential, env, ..., ifnotfound = list(NULL), inherits = inherits)
# remove not found
args <- args[sapply(args, Negate(is.null))]
# return found args and dots
c(args, dots)
}
f_a <- function(b, c = 0, ..., d = 1) {
b <- b + 1
c(b = b, c = c, d = d, ...)
}
f_e <- function() {
b <- 2
c <- 2
arg_list <- get_args_for(f_a, dots = list(5))
do.call(f_a, arg_list)
}
> f_e()
b c d
3 2 1 5
Setting inherits = FALSE
by default ensures that we only get variables from the specified environment.
We could also set dots = NULL
when calling get_args_for
so that we don't pass all variables,
but leave the ellipsis empty.
Nevertheless, it isn't entirely robust,
because dots
is simply appended at the end,
and if some arguments are not named,
they could end up matched by position.
Also, if some values should be NULL
in the call,
it wouldn't be easy to detect it.
I would strongly advise against using these below inside an R package. Not only will it be rather ugly, you'll get a bunch of notes from R's CMD check regarding undefined global variables.
Other options.
f_a <- function() {
return(b + c)
}
f_e <- function() {
b <- 2
c <- 2
# replace f_a's enclosing environment with the current evaluation's environment
environment(f_a) <- environment()
d <- f_a()
d
}
> f_e()
[1] 4
Something like the above probably wouldn't work inside an R package, since I think a package's functions have their enclosing environments locked.
Or:
f_a <- function() {
with(parent.frame(), {
b + c
})
}
f_e <- function() {
b <- 2
c <- 2
f_a()
}
> f_e()
[1] 4
That way you don't modify the other function's enclosing environment permanently. However, both functions will share an environment, so something like this could happen:
f_a <- function() {
with(parent.frame(), {
b <- b + 1
b + c
})
}
f_e <- function() {
b <- 2
c <- 2
d <- f_a()
c(b,d)
}
> f_e()
[1] 3 5
Where calling the inner function modifies the values in the outer environment.
Yet another option that is a bit more flexible,
since it only modifies the enclosing environment temporarily by using eval
.
However, there are certain R functions that detect their current execution environment through "daRk magic",
and cannot be fooled by eval
;
see this discussion.
f_a <- function() {
b <- b + 1
b + c
}
f_e <- function() {
b <- 2
c <- 2
# use current environment as enclosing environment for f_a's evaluation
d <- eval(body(f_a), list(), enclos=environment())
c(b=b, d=d)
}
> f_e()
b d
2 5
One option is to explicitly grab a
and b
from the calling environment:
f_a <- function(){
get('b', envir = parent.frame()) + get('c', envir = parent.frame())
}
f_e <- function(){
b = 2
c = 2
d = f_a()
d
}
f_e()
#> [1] 4
Alternatively, you can use quote
to delay evaluation and then eval
to evaluate the code in the calling environment, effectively doing the same thing:
f_a <- function(){
eval(quote(b + c), parent.frame())
}
This is not really a robust way to write code, though, as it limits the possible ways to call f_a
successfully. It's much easier to follow code that explicitly passes variables.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With