New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Defer on globalenv #76
Conversation
Maybe this should be called something like Maybe set the envir to |
I renamed it to It does not work to set the handler execution env to |
It looks like setting the handler execution env to |
If you're feeling positive about this, I'll add a test. |
Maybe we should also have a |
If you have to you could test these in a separate script in |
Maybe it could just be |
Two concrete motivating examples of wrapping withr technology to make test helpers that are easier to develop with. These helpers are faking the functionality that this PR would implement properly.
I actually think |
This is the sole use in the package and git blame suggests this function is the work of many hands at this point.
Avoids a potential infinite recursion problem when working with handlers, i.e. inspecting them
R/defer.R
Outdated
"clear with `clear_global_deferred()`." | ||
) | ||
} | ||
setting_on_self <- identical(envir, parent.frame()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem I remarked on earlier is actually more general, i.e. it's not unique to envir = .GlobalEnv
. If you set an event on environment A that is destined to be evaluated in environment A, you will have difficulty inspecting these handlers, due to recursion. For example, printing while debugging is impossible.
Although it's just a development matter, seems worth improving.
R/defer.R
Outdated
expr = substitute(expr), | ||
# add one level of indirection when capturing an environment in its | ||
# own handlers | ||
envir = if (setting_on_self) new.env(parent = envir) else parent.frame() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the second fix that I tried. See the commit history for a different approach that sets the environment to NULL
and has special handling in execute_handlers()
. I suspect this is better because it's more localized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that a new environment can work with on.exit()
. on.exit()
doesn't operate on environments; it operates on frames in the call stack. The environment here is passed on to do.call
which causes on.exit()
to be run in such a way that it can find the frame from the current environment. I don't think it can do that when you create an environment de novo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If your concern holds, is it surprising that this PR passes all tests, including one that tests the global env functionality specifically? (The R-devel failure is a pre-existing documentation glitch unrelated to this PR.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because not calling on.exit()
for the globalenv()
case is harmless. I don't understand the setting_on_self
problem, but I doubt this is the correct fix, so I'd recommend leaving as is for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the
setting_on_self
problem...
Here's is a demo you can do with with master
or the CRAN version. In general, you can't inspect an environment or an environment's handlers whenever it captures itself in a handler. Which is the default case.
library(withr)
local({
defer(print("hi"))
browser()
})
While in browser, try these commands to inspect the environment or its handlers:
environment() # Error: C stack usage 7971008 is too close to the limit
format(environment()) # works, prevents the recursion
names(attributes(environment())) # works
attr(environment(), "handlers") # Error: C stack usage ...
Any direct inspection of the environment or its attributes or the "handers"
attribute specifically results in infinite recursion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I finally tried this out, and I don't really think we need to worry about this recursive case. When debugging you can use str(environment(), max.level = 2)
or similar to break out of the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the root cause of the problem is something like this:
x <- environment()
attr(x, "env") <- x
x
This feels like somewhat of a bug in R to me, and I agree with Jim that it's probably not worth fixing. (If you really did want to fix it, I think a possible approach would be to temporarily add a class to the environment and define a print method for it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you really did want to fix it, I think a possible approach would be to temporarily add a class to the environment and define a print method for it
Ok I will revise to not worry about this.
Playing with this print method idea is what lead to my questions yesterday about how to get an environment's "memory address" style of label.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly max.level
seems to not be passed to attributes in str.default()
, so it doesn't work for Hadley's case, though it does work in Jenny's original example. Seems like a bug in str.default()
to me, it probably should have a (is.na(max.level) || nest.lev < max.level) &&
in the last conditional, but anyway...
@jimhester I think this is ready |
Here's a before vs. after demo, using Before This unintentionally reveals that Sys.setenv(FOO = "abc")
Sys.getenv("FOO")
#> [1] "abc"
withr::local_envvar(c(FOO = "xyz"))
#> Error in defer(set_envvar(old), envir = .local_envir): attempt to defer event on global environment
Sys.getenv("FOO")
#> [1] "xyz"
withr::defer(print("howdy!"))
#> Error in withr::defer(print("howdy!")): attempt to defer event on global environment
withr::run_global_deferred()
#> Error: 'run_global_deferred' is not an exported object from 'namespace:withr'
Sys.getenv("FOO")
#> [1] "xyz" After > Sys.setenv(FOO = "abc")
> Sys.getenv("FOO")
[1] "abc"
> withr::local_envvar(c(FOO = "xyz"))
Setting deferred event(s) on global environment.
* Execute (and clear) with `run_global_deferred()`.
* Clear (without executing) with `clear_global_deferred()`.
FOO
"abc"
> Sys.getenv("FOO")
[1] "xyz"
> withr::defer(print("howdy!"))
> withr::run_global_deferred()
[1] "howdy!"
> Sys.getenv("FOO")
[1] "abc" I couldn't use reprex for this because knitr also has a hand in where things are being evaluated and when they take effect. |
I think this also has interesting implications for scripts where you want to set lots of things to non-defaults and accumulate several events to restore things as you found them. By setting them with I'm deleting this comment because it's huge and again brings up interactions between knitr's execution model and withr. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be worth using reg.finalizer()
to ensure that the handlers on the global environment are at least run before R exits.
This sounds a bit scary. I could imagine having events registered here that are from failed experiments. If we use |
Isn't it just as dangerous to forget to call the clean up code potentially leaving things in a bad state? |
It depends on what we expect the primary usage to be. I'm thinking of test or function development, in which case it's just a lot of experimentation and I'd prefer the default to be "don't run unless asked". Perhaps this is the closest reference: You can call |
I worked against this PR in a usethis PR (r-lib/usethis#1107). All usethis tests pass and the interactive test development experience with respect to, e.g., |
…bute Downside of this: any env with deferred events cannot be inspected easily, due to infinite recursion problems.
OK I have done as requested in c980b09. I still disagree but I care more about getting this feature in general. As it stands, if someone sets a deferred event on I think this is done. |
An experiment for discussion. I'm finding this quite handy for developing tests that use withr intensely. A little top-level usage:
Created on 2018-07-01 by the reprex package (v0.2.0).
One unsavoury (?) aspect is that the handlers contain
.Globalenv
itself, because that isparent.frame()
whendefer()
is called. This doesn't keep me from playing with this branch, but it causes infinite recursion problems when inspecting the current handlers. I assume we could address that if you consider merging this.