Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

determine whether evaluation of an argument will fail due to non-existence

Tags:

r

eval

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.

like image 793
Ben Bolker Avatar asked Feb 18 '13 21:02

Ben Bolker


2 Answers

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
like image 119
Josh O'Brien Avatar answered Oct 01 '22 09:10

Josh O'Brien


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.

like image 41
regetz Avatar answered Oct 01 '22 08:10

regetz