Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recommendations for "Dynamic/interactive" debugging of functions in R?

Tags:

r

debugging

When debugging a function I usually use

library(debug)
mtrace(FunctionName)
FunctionName(...)

And that works quite well for me.

However, sometimes I am trying to debug a complex function that I don't know. In which case, I can find that inside that function there is another function that I would like to "go into" ("debug") - so to better understand how the entire process works.

So one way of doing it would be to do:

library(debug)
mtrace(FunctionName)
FunctionName(...)
# when finding a function I want to debug inside the function, run again:
mtrace(FunctionName.SubFunction)

The question is - is there a better/smarter way to do interactive debugging (as I have described) that I might be missing?

p.s: I am aware that there where various questions asked on the subject on SO (see here). Yet I wasn't able to come across a similar question/solution to what I asked here.

like image 997
Tal Galili Avatar asked Jul 09 '10 12:07

Tal Galili


3 Answers

Not entirely sure about the use case, but when you encounter a problem, you can call the function traceback(). That will show the path of your function call through the stack until it hit its problem. You could, if you were inclined to work your way down from the top, call debug on each of the functions given in the list before making your function call. Then you would be walking through the entire process from the beginning.

Here's an example of how you could do this in a more systematic way, by creating a function to step through it:

walk.through <- function() {
  tb <- unlist(.Traceback)
  if(is.null(tb)) stop("no traceback to use for debugging")
  assign("debug.fun.list", matrix(unlist(strsplit(tb, "\\(")), nrow=2)[1,], envir=.GlobalEnv)
  lapply(debug.fun.list, function(x) debug(get(x)))
  print(paste("Now debugging functions:", paste(debug.fun.list, collapse=",")))
}

unwalk.through <- function() {
  lapply(debug.fun.list, function(x) undebug(get(as.character(x))))
  print(paste("Now undebugging functions:", paste(debug.fun.list, collapse=",")))
  rm(list="debug.fun.list", envir=.GlobalEnv)
}

Here's a dummy example of using it:

foo <- function(x) { print(1); bar(2) }
bar <- function(x) { x + a.variable.which.does.not.exist }
foo(2)

# now step through the functions
walk.through() 
foo(2)

# undebug those functions again...
unwalk.through()
foo(2)

IMO, that doesn't seem like the most sensible thing to do. It makes more sense to simply go into the function where the problem occurs (i.e. at the lowest level) and work your way backwards.

I've already outlined the logic behind this basic routine in "favorite debugging trick".

like image 66
Shane Avatar answered Nov 20 '22 14:11

Shane


I like options(error=recover) as detailed previously on SO. Things then stop at the point of error and one can inspect.

like image 22
Dirk Eddelbuettel Avatar answered Nov 20 '22 13:11

Dirk Eddelbuettel


(I'm the author of the 'debug' package where 'mtrace' lives)

If the definition of 'SubFunction' lives outside 'MyFunction', then you can just mtrace 'SubFunction' and don't need to mtrace 'MyFunction'. And functions run faster if they're not 'mtrace'd, so it's good to mtrace only as little as you need to. (But you probably know those things already!)

If 'MyFunction' is only defined inside 'SubFunction', one trick that might help is to use a conditional breakpoint in 'MyFunction'. You'll need to 'mtrace( MyFunction)', then run it, and when the debugging window appears, find out what line 'MyFunction' is defined in. Say it's line 17. Then the following should work:

D(n)> bp( 1, F) # don't bother showing the window for MyFunction again D(n)> bp( 18, { mtrace( SubFunction); FALSE}) D(n)> go()

It should be clear what this does (or it will be if you try it).

The only downsides are: the need to do it again whenever you change the code of 'MyFunction', and; the slowing-down that might occur through 'MyFunction' itself being mtraced.

You could also experiment with adding a 'debug.sub' argument to 'MyFunction', that defaults to FALSE. In the code of 'MyFunction', then add this line immediately after the definition of 'SubFunction':

if( debug.sub) mtrace( SubFunction)

That avoids any need to mtrace 'MyFunction' itself, but does require you to be able to change its code.

like image 3
Mark Bravington Avatar answered Nov 20 '22 13:11

Mark Bravington