The function base::substitute(expr, env)
, as per its documentation page,
returns the parse tree for the (unevaluated) expression expr, substituting any variables bound in env.
I am looking for a way of substituting any variables bound not in one specific environment, but in all environments in the current call stack, i.e. all environments encountered by iterating over parent.frame(i)
where i
is in seq_len(sys.nframe())
. Additionally, I'd like standard scoping rules to apply.
This is a contradiction: standard scoping in R is lexical, but what I describe here is dynamic scoping (thank you @MikkoMarttila for helping me clear this up). What I actually want is a way of substituting any variables bound not in one specific environment, but in all parent enclosing environments, the set of which can be enumerated by repeatedly applying base::parent.env()
.
Consider the following example:
do_something <- function(todo) {
cat(
paste(
deparse(substitute(todo, environment())),
collapse = "\n"
)
)
}
nested_do <- function() {
var_2 <- "goodbye"
do_something({
print(var_1)
print("world")
print(var_2)
})
}
var_1 <- "hello"
nested_do()
Currently this gives
print(var_1)
print("world")
print(var_2)
where I'd like to have
print("hello")
print("world")
print("goodbye")
I have looked at base::bquote()
and rlang::enexpr()
but for both I have to explicitly mark the variables for substitution/unquoting with .()
or !!
. I'd rather not have to specify variables manually, but have everything resolved that is found (just like in base::substitute()
). Furthermore, I tried iteratively applying base::substitute()
with the respective env
arguments and I had a look at oshka::expand()
, but nothing I tried, does what I need.
Any help is much appreciated.
What I'm trying to achieve is the following: I'm working on a cluster running LSF. This means that I can submit jobs using the submission tool bsub
which may take an R file as input. Now I would like to have a script that generates these input files (e.g. using the function do_something()
).
long_running_fun <- function(x) {
Sys.sleep(100)
x / 2
}
var_1 <- 2 + 2
var_2 <- var_1 + 10
do_something({
print(var_1)
var_3 <- long_running_fun(var_2)
print(var_3)
})
I in the above case, want the following (or something equivalent) to be written to a file
print(4)
var_3 <- long_running_fun(14)
print(var_3)
Building on @MikkoMarttila's answer, I think the following does what I requested
do_something <- function(todo) {
# A helper to substitute() in a pre-quoted expression
substitute_q <- function(expr, env) {
eval(substitute(substitute(x, env), list(x = expr)))
}
substitute_parents <- function(expr) {
expr <- substitute(expr)
# list all parent envs
envs <- list()
env <- environment()
while (!identical(env, globalenv())) {
envs <- c(envs, env)
env <- parent.env(env)
}
# substitute in all parent envs
for (e in envs) {
expr <- substitute_q(expr, e)
}
# previously did not include globalenv() and
# substitute() doesnt "substitute" there
e <- as.list(globalenv())
substitute_q(expr, e)
}
cat(
paste(
deparse(substitute_parents(todo)),
collapse = "\n"
)
)
}
This gives
nested_do <- function() {
var_2 <- "not_this"
do_something({
print(var_1)
Sys.sleep(100)
print("world")
print(var_2)
})
}
var_1 <- "hello"
var_2 <- "goodbye"
do_something({
print(var_1)
Sys.sleep(100)
print("world")
print(var_2)
})
#> {
#> print("hello")
#> Sys.sleep(100)
#> print("world")
#> print("goodbye")
#> }
nested_do()
#> {
#> print("hello")
#> Sys.sleep(100)
#> print("world")
#> print("goodbye")
#> }