简体   繁体   中英

Nicer way to build `call` from `list` and get a nice `traceback`

I am building a call in R from a list with named arguments to the function which elements (amount of elements and names of elements) may vary. The motivation to use a call is to get a nicer traceback() in case of an error than with do.call . Here is an example

set.seed(1)
X <- structure(replicate(3, rnorm(200), simplify = FALSE), 
               names = c("X1", "X2", "X3"))
str(X)
#R List of 3
#R  $ X1: num [1:200] -0.626 0.184 -0.836 1.595 0.33 ...
#R  $ X2: num [1:200] 0.409 1.689 1.587 -0.331 -2.285 ...
#R  $ X3: num [1:200] 1.074 1.896 -0.603 -0.391 -0.416 ...

func <- function(...){
  # ... actual code
  if(TRUE) # some error
    stop("Boh :(")
}

# `do.call` gives an un-nice traceback
do.call(func, X)
#R  Error in func(X1 = X$X1, X2 = X$X2, X3 = X$X3) : Boh :( 
traceback()
#R 3: stop("Boh :(") at #3
#R 2: (function (...) 
#R   {
#R       if (TRUE) 
#R           stop("Boh :(")
#R   })(X1 = c(-0.626453810742332, 0.183643324222082, -0.835628612410047, 
#R   1.59528080213779, 0.329507771815361, -0.820468384118015, 0.487429052428485, 
#R   0.738324705129217, 0.575781351653492, -0.305388387156356, 1.51178116845085, 
#R   0.389843236411431, -0.621240580541804, -2.2146998871775, 1.12493091814311, 
#R   -0.0449336090152309, -0.0161902630989461, 0.943836210685299, 
#R   0.821221195098089, 0.593901321217509, 0.918977371608218, 0.782136300731067, 
#R   0.0745649833651906, -1.98935169586337, 0.61982574789471, -0.0561287395290008, 
#R   -0.155795506705329, -1.47075238389927, -0.47815005510862, 0.417941560199702, 
#R [output abbreviated. There are many more lines] 

# we can build a call instead like 
cl <- list(quote(func))
na <- names(X) 
cl[na] <- lapply(na, function(z) substitute(X$z, list(z = as.symbol(z))))
(cl <- as.call(cl)) 
#R func(X1 = X$z, X2 = X$z, X3 = X$z)

# now we get a nice traceback
eval(cl)
#R  Error in func(X1 = X$X1, X2 = X$X2, X3 = X$X3) : Boh :( 
traceback()
#R 4: stop("Boh :(") at #3
#R 3: func(X1 = X$X1, X2 = X$X2, X3 = X$X3)
#R 2: eval(cl)
#R 1: eval(cl)

What bothers is that the do.call method can be done in one line where as the call version takes a few lines. Can this be done smarter while still getting a nice (short and readable) traceback() ? I know I can wrap the call version into a function so this is not the solution I am looking for.

I know you said you don't want an extra function, but you could keep your code intact and use trace to use your new function when debugging:

do_call <- function(fun,X){
  # remove these comments to use as a simple replacement for do.call
  # cl <- list(substitute(fun))
  # na <- names(X) 
  cl[na] <- lapply(na, function(z) substitute(X$z, list(z = as.symbol(z))))
  (cl <- as.call(cl)) 
  eval(cl)
}

trace(do.call, quote(return(eval.parent(do_call(substitute(what),substitute(args))))))

do.call(func, X)
#R  Error in func(X1 = X$X1, X2 = X$X2, X3 = X$X3) : Boh :( 

traceback()
# 12: stop("Boh :(") at #3
# 11: func(X1 = X$X1, X2 = X$X2, X3 = X$X3)
# 10: eval(cl)
# 9: eval(cl) at #6
# 8: do_call(substitute(what), substitute(args))
# 7: eval(expr, p)
# 6: eval.parent(do_call(substitute(what), substitute(args)))
# 5: eval(expr, p)
# 4: eval(expr, p)
# 3: eval.parent(exprObj)
# 2: .doTrace(return(eval.parent(do_call(substitute(what), substitute(args)))), 
#        "on entry")
# 1: do.call(func, X)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM