I want to write a background program that detects changes to R's global environment and alerts the user if any variable is being overwritten (during an interactive session).
I've tinkered with the following code:
environment_changed <- function(before_env, after_env) {
!identical(before_env, after_env)
}
check_environment_change <- function(before_env) {
after_env <- as.list(.GlobalEnv)
if (has_environment_changed(before_env, after_env)) {
print("Change detected!")
}
}
addTaskCallback(check_environment_change)
x <- 1
x <- 2 # doesn't work
It doesn't work. I do not really understand how addTaskCallback
works, but I thought this might be one possible approach. How else might this be achieved?
Using the example in addTaskCallback()
as a template, I made the following. It uses the times()
argument to identify how many times the additional callback will run (which I have currently set to Inf
, so you'll have to shut it off with removeTaskCallback()
. What it currently does is check the environment for names that are the same before and after the execution of the task. Then, it returns a message if any of those objects have changed. Here's an example:
times <- function(total = Inf, before_env = as.list(.GlobalEnv)) {
ctr <- 0
function(expr, value, ok, visible) {
check_environment_overwrite <- function(before_env) {
after_env <- as.list(.GlobalEnv)
olaps <- intersect(names(before_env), names(after_env))
if(length(olaps) > 0){
if(!identical(before_env[olaps], after_env[olaps])){
out <- lapply(seq_along(olaps), \(i){
identical(before_env[[olaps[i]]],
after_env[[olaps[i]]])})
outv <- do.call("c", out)
names(outv) <- olaps
cat("The following elements have changed in .GlobalEnv: ",
names(outv)[which(!outv)], "\n", sep="")
}
}
}
check_environment_overwrite(before_env)
before_env <<- as.list(.GlobalEnv)
ctr <<- ctr + 1
keep.me <- (ctr < total)
if (!keep.me)
cat("handler removing itself\n")
# return
keep.me
}
}
To get the same result, start with a workspace that doesn't include x
or z
:
# > n <- addTaskCallback(times())
# > x <- 1
# > z <- 1
# > x <- 2
# The following elements have changed in .GlobalEnv: x
# > z <- 1
# > z <- 2
# The following elements have changed in .GlobalEnv: z
# >
# > removeTaskCallback(n)
# [1] TRUE
In the spirit of the question, the function above will notify you after you overwrote something. We could make an alternative assignment operator that looks to see whether something will be overwritten and if it will be, it asks the user if they want to overwrite it.
`%a%` <- function(lhs, rhs){
lhs_name <- as.character(substitute(lhs))
if(lhs_name %in% names(.GlobalEnv)){
rpl <- askYesNo(paste0("Do you want to replace ", lhs_name, " in .GlobalEnv"))
if(rpl){
assign(lhs_name, rhs, envir = .GlobalEnv)
}
}else{
assign(lhs_name, rhs, envir = .GlobalEnv)
}
}
# > x %a% 1
# > x %a% 2
# Do you want to replace x in .GlobalEnv (Yes/no/cancel) Yes
# > x
# [1] 2
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