Check for missing argument in parent function

Question

I have a function f that computes a summary of the environment in which it is called. In this trivial example, it simply sums up 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 a function with missing arguments, it throws 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 handle this, I need a method to indicate from f if y or some other arbitrary object in g is the argument of g , in which case if it is absent.

My attempts so far

To try different solutions, I do

 debug(f) g() 

Of course, missing(y) does not work, since y not an argument for f . Changing the environment in which missing is evaluated also does not work, since we are still at the same level in 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 argument to g 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 

which gives TRUE for both x and y , but not for the regular z variable. Combining it with tryCatch , which checks if the argument can be restored, will solve the problem, but it's 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, unlike the explicitly specified "y" in the above example.

Update: Goal

In reality, the goal of f is to debug g at runtime. I could call

 R> debug(g) R> g() 

go through it and check the state of the objects with f , or I can set options(error=recover) and just find the debug g if there is an error. In both cases, there should be a well-defined call stack, so I assume that my main question is whether it can be requested at different levels, similar to the frame stack (accessed via sys.frames() ). I must admit that for me it is deep waters.

Think of f as my own modified version of ls.str , which can be used as follows:

 Browse[2]> ls.str() # Inside g() x : num 7 y : <missing> 

After some digging into ls.str and utils:::print.ls_str I found out that it performs the same task

 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)) } } 

If there is a right way to do this, I will just do a similar hack.

+6
source share
2 answers

The values โ€‹โ€‹of the missing arguments are presented in the pair list associated with the environment using an odd object known as the "empty character". It turns out that at least for now the "empty character" is also returned by calling quote(expr=) . ( See here for one discussion of the empty character.)

The ls_safe() function uses both of these facts to implement an alternative absence test. It returns a character vector without missing variables present in the environment specified by its pos argument.

 ls_safe <- function(pos=1) { ## Capture the parent environment 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 
+3
source

I had the same problem: I need a function that checks for the absence of an argument. I also first tried the eval idea, which always gave me FALSE , even when the variable was missing.

Josh presented the solution above, but it is too specific. What I wanted was a neat check function, which I can add to my functions so that they check for the absence of necessary variables and produce information errors. Here is 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") } 

Check this:

 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 that I am not satisfied with is that the error has the wrong message โ€œCalledโ€. It returns a checker function, but it would be more informative if it returned a parent function. I do not know if this can be fixed.

Hope this can help someone.

+1
source

Source: https://habr.com/ru/post/980661/


All Articles