I have an expression expr
that I want to evaluate; the symbol/value pairs I need to evaluate it may be in one (or more!) of three environments, and I'm not sure which. I'd like to find a convenient, efficient way to "chain" the environments. Is there a way to do this safely while avoiding the copying of contents of environments?
Here's the setup:
env1 <- list2env(list(a=1))
env2 <- list2env(list(b=1))
env3 <- list2env(list(c=1))
expr <- quote(a+b)
So, I will need to evaluate expr
in the combination of environments env1
and env2
(but I don't necessarily know that). Any of eval(expr, env1)
; eval(expr, env2)
; and eval(expr,env3)
will fail, because none of those environments contains all of the required symbols.
Let's suppose I'm willing to assume that the symbols are either in env1
+env2
or in env1
+env3
. I could:
problems:
parent.env()<-
could be a bad idea — as described in ?parent.env
:The replacement function parent.env<- is extremely dangerous as it can be used to destructively change environments in ways that violate assumptions made by the internal C code. It may be removed in the near future.
(although, according the source history, that warning about removal "in the near future" is at least 19 years old ...)
(in fact I've already managed to induce some infinite loops playing with this approach)
tryCatch(eval(call, envir=as.list(expr1), enclos=expr2),
error=function(e) {
tryCatch(eval(call, as.list(expr1), enclos=expr3))
to create an "environment within an environment"; try out the combined pairs one at a time to see which one works. Note that enclos=
only works when envir
is a list or pairlist, which is why I have to use as.list()
.
problem: I think I still end up copying the contents of expr1
into a new environment.
I could use an even more deeply nested set of tryCatch()
clauses to try out the environments one at a time before I resort to copying them, which would help avoid copying where unnecessary (but seems clunky).
Convert the enviroments to lists, concatenate them and use that as the second arg of eval
. Note that this does not modify the environments themselves.
L <- do.call("c", lapply(list(env1, env2, env3), as.list))
eval(expr, L)
## [1] 2
Also note that this does not copy the contents of a
, b
and c
. They are still at the original addresses:
library(pryr)
with(env1, address(a))
## [1] "0x2029f810"
with(L, address(a))
## [1] "0x2029f810"
No, there's no simple way to chain environments. As you know, every environment has a parent which is another environment, so overall environments form a tree structure. (The root of the tree is the empty environment.) You can't easily take a leaf from a tree and graft it onto another leaf without making structural changes to it.
So if you really need to evaluate your expression in the way you describe, you're going to have to parse it, look up the names yourself, and substitute values into it. But even this isn't necessarily going to give you the same value at the end, because substitute()
and similar functions might be involved in it.
My advice would be to start over, and don't try to make an expression like the one you're talking about. This might involve copying, but remember that copying is usually cheap in R: the cost only comes if you modify one of the copies.
Edited to add:
The current other four answers are implicitly making assumptions that env1
to env3
are as simple as they are in your example. If that's true, then I'd go with @G.Grothendieck's solution. But all fail in this simple variation on your example:
env1 <- list2env(list(a=1))
env2parent <- list2env(list(b=1))
env2 <- new.env(parent = env2parent)
env3 <- list2env(list(c=1))
expr <- quote(a+b)
I can evaluate quote(b)
using eval(quote(b), envir = env2)
, but I can't evaluate expr
using the other solutions unless I also include env2parent
in the list of environments being passed.
Edited again:
Here's a solution that essentially does what I suggested, except instead of parsing, it uses the all.vars
function from one of @r2evans answers. It works by copying all the variables into a common environment, so copying happens, but the names are kept:
envfunc3 <- function(expr, ...) {
vars <- all.vars(expr)
env <- new.env()
for (v in vars) {
for (e in list(...))
if (exists(v, envir = e)) {
assign(v, get(v, envir = e), envir = env)
break
}
}
eval(expr, envir=env)
}
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