Assume I have a function where I want to tweak the behavior a little bit (skip a certain line, say). I could
debug
to change the code interactively.trace
to inject own code.I am only interested in Option 3. for this post.
For instance, let's assume the following very simple code:
f <- function(dbg) {
x <- rnorm(1)
if (dbg) {
cat("Message 1\n")
}
if (x < 0 & dbg) {
cat("Message 2\n")
}
x
}
If (for whatever reason) I want to skip Message 2
(but keep Message 1
) I could use trace
as follows:
set.seed(12231)
untrace(f)
f(TRUE)
# Message 1
# Message 2
# [1] -0.5787277
trace(f, quote(dbg <- FALSE), at = 4, print = FALSE)
set.seed(12231)
f(TRUE)
# Message 1
# [1] -0.5787277
Now assume a slightly different function g
:
g <- function() {
x <- rnorm(1)
cat("Message\n")
cat("Message 2\n")
x
}
Conceptually I would like to do something like:
trace(g, quote(if(FALSE)), at = 4, print = FALSE)
but this does not work obviously.
Is there a way how I could use trace
to skip (a) certain line(s)? Or is my only option to copy, paste and edit the function?
Effectively all trace
does (for regular functions) is insert a call to .doTrace
at the desired point inside the function body. We can do the same manually to achieve the exact same effect.
But in your case we want to remove instead of add; and we can do this as well:
modified_body <- body(g)
modified_body[4] <- NULL
original_body <- body(g)
body(g) <- modified_body
g()
# “untrace”:
body(g) <- original_body
I tried getting this to work with trace
, by passing the modified body as the tracer
. Unfortunately I cannot prevent the original function from executing afterwards: adding an explicit return()
call to the tracer
does nothing (since .doTrace
uses eval.parent
to evaluate the tracer
code, and return
inside eval
does not exit the calling scope). I can use stop()
instead of return()
. However, this is undesirable since it always drops to the top level rather than to the caller of g
. I tried to influence this by installing a globalCallingHandler
but there is no appropriate restart I could invoke, and at any rate regular restarts would drop us right back into g
rather than its caller.
I don’t know if this could be circumvented even in principle: it would essentially require modifying the active call stack — which restarts do, but only in very limited ways as far as I know (namely, by dropping the entire call stack via the abort
restart, or by resuming inside the innermost call frame).