I have a function f
that calculates a summary of the environment in which it is called. In this trivial example it just sums all the objects found.
f <- function(){
x <- ls(parent.frame())
sum(sapply(x, get, envir=parent.frame()))
}
g <- function(x = 7, y){
z <- 3
f()
}
However, if called from within a function with missing arguments it will throw an error.
R> g(y = 34)
[1] 44
R> g()
Error in FUN(c("x", "y", "z")[[2L]], ...) :
argument "y" is missing, with no default
To deal with it appropriately, I need a method to tell, from within f
, if y
or some other arbitrary object in the environment of g
is an argument to g
and in that case if it is missing.
To try different solutions I do
debug(f)
g()
Of course missing(y)
does not work since y
is not an argument to f
. Changing the environment in which missing
is evaluated doesn't work either, since we are still on the same level of the call stack:
Browse[2]> eval(missing(y), parent.frame())
Error in missing(y) : 'missing' can only be used for arguments
Browse[2]> identical(sys.frames(), eval(sys.frames(), parent.frame()))
[1] TRUE
What I can do is determine if y
is an argument to g
function using a dirty hack
Browse[2]> eval(substitute(missing(a), list(a="x")), parent.frame())
[1] TRUE
Browse[2]> eval(substitute(missing(a), list(a="y")), parent.frame())
[1] TRUE
Browse[2]> eval(substitute(missing(a), list(a="z")), parent.frame())
[1] FALSE
that yields TRUE
for both arguments x
and y
but not the ordinary variable z
. Combining it with a tryCatch
that checks if the argument can be retrieve would solve the problem, but it is terribly dirty:
is.argument <- eval(substitute(missing(a), list(a="y")), parent.frame())
if(is.argument){
tryCatch({
get("y", parent.frame())
FALSE
}, error = function(e) TRUE)
} else {
NA
}
Moreover, I cannot figure out how to define is.argument
for an arbitrary argument, as opposed to the explicitly stated "y"
in the example above.
In reality, the purpose of f
is to debug g
during runtime. I might call
R> debug(g)
R> g()
step through it and inspect the state of the objects with f
, or I might set options(error=recover)
and just find myself debugging g
if it produced an error. In both cases there should be a clearly defined call stack, so I guess my underlying question is if it can be queried on different levels, in a similar way to the frame stack (accessed with sys.frames()
). I must confess though that this is deep waters for me.
Think of f
as my own tweaked version of ls.str
, which can be used like this:
Browse[2]> ls.str() # Inside g()
x : num 7
y : <missing>
After some digging in ls.str
and utils:::print.ls_str
I found out that it accomplishes the same task by
for (nam in x) {
cat(nam, ": ")
o <- tryCatch(get(nam, envir = E, mode = M), error = function(e) e)
if (inherits(o, "error")) {
cat(if (length(grep("missing|not found", o$message)))
"<missing>"
else o$message, "\n", sep = "")
} else {
strO <- function(...) str(o, ...)
do.call(strO, strargs, quote = is.call(o) || is.symbol(o))
}
}
Unless there is a proper way to do this I'll just make a similar hack.
The values of missing arguments are represented in the pairlist associated with an environment by an odd object known as the "empty symbol". It turns out that, at least at present, the "empty symbol" is also returned by a call to quote(expr=)
. (See here for one discussion of the empty symbol.)
The function ls_safe()
uses both of those facts to implement an alternative test of missingness. It returns a character vector of non-missing variables present in the environment specified by its pos
argument.
ls_safe <- function(pos=1) {
## Capture the parent environment's frame as a list
ll <- as.list(parent.frame(pos))
## Check for "missing" variables
ii <- sapply(ll, function(X) identical(X, quote(expr=)))
names(ll)[!ii]
}
## Then just use ls_safe() in place of ls()
f <- function(){
x <- ls_safe(pos=2)
sum(sapply(x, get, envir=parent.frame()))
}
g <- function(x = 7, y){
z <- 3
f()
}
g(99)
## [1] 102
g(99, 1000)
## [1] 1102
I had the same problem: wanting a function that checks if an argument is missing. I also first tried the eval
-based idea, which always gave me FALSE
even when the variable was missing.
Josh provided a solution above, but it is too specific. What I wanted was a neat checker function I can add to my functions, so that they check for missingness in necessary variables and throws informative errors. Here's my solution:
check_missing = function(var_names, error_msg = "[VAR] was missing! Please supply the input and try again.") {
#parent.frame as list
pf = as.list(parent.frame())
#check each if missing
for (name in var_names) {
#is it there at all?
if (!name %in% names(pf)) {
stop(name + " is not even found in the parent.frame! Check the variable names!", call. = F)
}
#check if missing
if (are_equal(pf[[name]], quote(expr = ))) {
stop(str_replace(error_msg, pattern = "\\[VAR\\]", name), call. = F)
}
}
#all fine
return(invisible(NULL))
}
To test it, use this:
test_func = function(y) {
check_missing("y")
print("OK")
}
Test it:
test_func(y = )
# Error: y was missing! Please supply the input and try again.
# Called from: check_missing("y")
test_func(y = "k")
# [1] "OK"
The remaining thing I'm unsatisfied with is that the error has the wrong "Called from" message. It returns the checker function itself, but it would be more informative if it returned the parent function. I don't know if this can be fixed.
Hope this may be of use to someone.
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