I have a rather complicated situation where I may end up passing a variable that doesn't exist to a function. I have a pretty good idea when this will happen, and fixing it will be difficult; I would be satisfied with being able to detect the condition reliably and executing a workaround in that case. I could simply use
inherits(try(eval(possibly_missing_variable),silent=TRUE),"try-error")
but if possible I would like to test for the specific condition that leads to the error object 'possibly_missing_variable' not found
. (I could try to grep for "not found" in the error message, but I have been admonished on the r-devel list in the past that this will fail if R is being run in a different language so that a translated version of the error message appears.)
I've tried various combinations of deparse(substitute(...))
, but they don't seem to run far enough up the call stack. Here's my best shot at a reproducible example:
f <- function(d) {
## test here
cat("'d' exists:",exists("d"),"\n") ## TRUE
cat("deparse(substitute(d)):",dd <- deparse(substitute(d)),"\n") ## OK
cat("exists('",dd,"'): ",exists(dd),"\n",sep="")
eval(d)
}
f2 <- function(ddd) {
f(ddd)
}
ddd <- 5
f2(junk)
The results are:
'd' exists: TRUE
deparse(substitute(d)): ddd
exists('ddd'): TRUE
Error in eval(d) : object 'junk' not found
I want a test that will correctly inform me (before hitting the error) that the evaluation will fail because the relevant object can't be found anywhere in the stack of environments/enclosing environments etc.. Any ideas ... ?
More generally, is there a way to figure out the farthest-upstream name of an argument ("junk"
in this case)? If I could do that, then exists(farthest_upstream_name)
would solve my problem.
Perhaps something like this (stolen from my second answer to this question)?
f <- function(d) {
## test here
ff <- sys.frames()
ex <- substitute(d)
ii <- rev(seq_along(ff))
for(i in ii) {
ex <- eval(substitute(substitute(x, env=sys.frames()[[n]]),
env = list(x = ex, n=i)))
}
if(!exists(deparse(ex))) stop("Substitute real error action here")
eval(d)
}
f2 <- function(ddd) {
f(ddd)
}
ddd <- 5
f2(junk)
## Error in f(ddd) (from #10) : Substitute real error action here
I like Josh O'Brien's approach. However, using f
as defined there, the "non-existence" error condition can be triggered even when the passed name does in fact have an active binding, if it was created local to some function within the call stack (rather than in the global environment). Also, though perhaps not specifically relevant to Ben's intended usage, the error will be triggered if the argument is an expression, not just a name.
A simple fix is to tweak Josh's function to include an is.symbol
test:
f <- function(d) {
## test here
ff <- sys.frames()
ex <- substitute(d)
ii <- rev(seq_along(ff))
for(i in ii) {
ex <- eval(substitute(substitute(x, env=sys.frames()[[n]]),
env = list(x = ex, n=i)))
}
if(is.symbol(ex) && !exists(deparse(ex))) {
stop("Substitute real error action here")
}
eval(d)
}
The desired check still works:
f2 <- function(ddd) {
f(ddd)
}
f2(junk)
## Error in f(ddd) : Substitute real error action here
But the following two cases now pass through rather than yielding the error:
# case 1: argument to f is local to a calling function
f3 <- function() {
notjunk <- 999
f(notjunk)
}
f3()
## [1] 999
# case 2: argument to f is an expression
f2(5+5)
## [1] 10
What's happening in f
is that after the repeated application of call-substitute-substitute, ex
is set to the evaluated argument itself in case 1 above, and to the passed (albeit still unevaluated) call in case 2. In both cases, the exists
test alone would fail because ex
is not actually a name (aka symbol), but clearly non-existence is not a concern if we've been able to resolve beyond the name.
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