3

Following the recent discussions here (e.g. 1, 2 ) I am now using environments in some of my code. My question is, how do I create functions that modify environments according to its arguments? For example:

y <- new.env()
with(y, x <- 1)
f <- function(env,z) {
    with(env, x+z)
}
f(y,z=1)

throws

Error in eval(expr, envir, enclos) : object 'z' not found

I am using environments to keep concurrently two sets of simulations apart (without refactoring my code, which I wrote for a single set of experiments).

Community
  • 1
  • 1
Eduardo Leoni
  • 8,991
  • 6
  • 42
  • 49
  • 2
    Is there a reason to use `with`? If not then you could use `env$x+z`. – Marek Apr 15 '10 at 15:15
  • In my function I am referencing a lot of objects inside the environment, hence the `with`. But this is a good solution and I might use it. – Eduardo Leoni Apr 15 '10 at 15:28
  • 1
    What are you trying to do? I suspect whatever you are trying to achieve will be best done without explicit reference to environments. In the 15+ packages I've written and the numerous data analysis I've done, I've only had to explicitly use environments a handful of times. – hadley Apr 15 '10 at 16:07
  • @hadley, oh I am sure there's a better way. But I have a deadline and, as I said, and environments allow me to rewrite a smaller part of my code. – Eduardo Leoni Apr 15 '10 at 16:25
  • 1
    I understand the importance of deadlines, but if you proceed down this path you code is going to get harder and harder to understand and harder and harder to adapt when you have new requirements. – hadley Apr 15 '10 at 17:31
  • @hadley: Actually, the code is surprisingly clean. I am doing MCMC for two different datasets (years). I need only the summaries, so there's no point in having the simulations in the same matrix. Using environments I don't have to name things like data_2002, mcmc_2002, b_2002, etc. But I am sure there are other, perhaps better, ways to deal with this. Thanks for the comments / admonishments. – Eduardo Leoni Apr 15 '10 at 19:14

3 Answers3

8

The simplest solution is to use the environment when referencing the object:

y <- new.env()
y$x <- 1
f <- function(env,z) {
    env$x+z
}
f(y,z=1)

You would need to assign z to your environment as well.

y <- new.env()
with(y, x <- 1)
f <- function(env,z) {
    assign("z", z, envir=env)
    with(env, x+z)
}
f(y,z=1)

One other option would be to attach your environment so that the variables can now be used directly.

y <- new.env()
with(y, x <- 1)
f <- function(env,z) {
    attach(env)
    y <- x + z
    detach(env)
    y
}
f(y,z=1)

This latter solution is powerful because it means you can use any object from any attached environment within your new environment, but it also means that you need to be very careful about what has been assigned globally.

Edit:

This is interesting, and I don't entirely understand the behavior (i.e. why z is not in the scope of the with call). It has something to do with the creation of the environment originally that is causing it to be outside the scope of the function, because this version works:

f <- function(z) {
    y <- new.env()
    with(y, x <- 1)
    with(y, x+z)
}
f(y,z=1)
Shane
  • 98,550
  • 35
  • 224
  • 217
  • Thanks. I was using the solution with attach. But when the function encounters an error before detaching, I get the typical attach problems. Assign works. I'd rather z be temporary (like in a regular function), though. I can't have it both ways, I guess. – Eduardo Leoni Apr 15 '10 at 15:20
  • See my updated answer. Also, if you encounter a problem, wrap everything in a try or tryCatch block, and detach it regardless. – Shane Apr 15 '10 at 15:21
  • 1
    What's the reason that z isn't in the search path in the OP's code? From the docs for with(): "The environment has the caller's environment as its parent." Also, with(y,x+z) seems to work at the top level. I'm somewhat puzzled by why this fails in the function call. – Leo Alekseyev Apr 15 '10 at 15:32
  • Shane, is there a way to reference the function's scope? – Eduardo Leoni Apr 15 '10 at 15:38
  • Eduardo: that's a great question. I don't know of a way, although maybe someone else does? – Shane Apr 15 '10 at 15:43
  • @Leo: That's also an awesome question. I would also have expected `z` to be in the scope of the function, and the function scope to be a parent to the with call. – Shane Apr 15 '10 at 15:53
  • Eduardo/Shane: well, 'sys.frame' returns the calling environment (and 'sys.frames' will return *all* environments on the stack). – doug Apr 15 '10 at 16:59
  • 2
    The reason is that when 'z' is evaluated in 'env' and not found, the next place it looks is the enclosing environment/scope. The enclosing scope is not f() but the global environment, because lexical or static scoping rules say that the enclosing scope is determined at the time of creation (hence static), not at evaluation (dynamic scope). – hatmatrix Apr 16 '10 at 10:29
  • Regarding my previous comment, "the enclosing scope is not f() but the global environment" - that's in the case of the original formulation. :-P – hatmatrix Apr 16 '10 at 10:48
  • Also, you can reference the function's scope with environment(), but doesn't work when you invoke it within the call to with() within the function. – hatmatrix Apr 16 '10 at 10:50
3

You only need to make one change to make your example work - redefine your function to use substitute() to 'fix' the desired values within the scope of f():

f <- function(env,z) {
    eval(substitute(x+z,list(z=z)), env)
}

This can quickly get murky especially since you can even include assignment statements within substitute() (for instance, replace x+z with y <- x+z, not that this is entirely relevant here) but that choice can be made by the developer...

Additionally, you can replace list(z=z) in the substitution expression above with environment() (e.g., substitute(x+z,environment())) as long as you don't have conflicting variable names between those passed to f() and those residing in your 'env', but you may not want to take this too far.

Edit: Here are two other ways, the first of which is only meant to show the flexibility in manipulating environments and the second is more reasonable to actually use.

1) modify the enclosing environment of 'env' (but change it back to original value before exiting function):

f <- function(env,z) {
  e <- environment(env)
  environment(env) <- environment()
  output <- with(env,x+z)
  environment(env) <- e
  output
}

2) Force evaluation of 'z' in current environment of the function (using environment()) rather than letting it remain a free variable after evaluation of the expression, x+z, in 'env'.

f <- function(env,z) {
  with(environment(),with(env,x+z))
}

Depending on your desired resolution order, in case of conflicting symbol-value associations - e.g., if you have 'x' defined in both your function environment and the environment you created, 'y' (which value of 'x' do you want it to assume?) - you can instead define the function body to be with(env,with(environment(),x+z)).

hatmatrix
  • 42,883
  • 45
  • 137
  • 231
1
 y <- new.env()
 with(y, x <- 1)
 f <- function(env,z) {
    with(env, x+z)
 }
 f(y,z=1)

mind the parentheses:) The following will work:

with(env, x)+z
Kenn
  • 11
  • 1