This is an extension of my comment above. As said in the comment checkout this guide for more detail.
To inspect calls dynamically during calls, editing functions and adding code can be done using the trace(func, edit = TRUE) method. However it is not the recommended methodology for doing so in any programming language. It is however recommended to instead perform debugging. Luckily debugging in R is simpler than many other languages. Some of the most noticeable methods for debugging in R is obtained using
debug(func) or debugonce(func)
trace
browser
I'll ignore trace's main usage, and only pass over it briefly in conjunction with browser.
Take this small example code
x <- 3
f <- function(x){
x <- x ** 2
x
}
Now if we wanted to "go into" this function and inspect what happens we can use the debug method simply by doing
debug(f) # alt: debugonce(f)
f(x)
and the following shows in the console
debugging in: f(x)
debug at #1: {
x <- x^2
x
}
We see 2 things: The original call in line 1, the function debugged including a function-line-number (#1) with the body in the function (potentially truncated). In addition the command has changed to Browse[n] (where n is a number), indicating we are in the debugger.
At this point nothing has run so in the "environment" tab in Rstudio we can see x | x. Note that x is still a promise (its value is delayed until used or changed). If we execute x we get the value [1] 3 and we see the environment change to x | 3 (it has been forced, and is no longer a promise).
Inside the debugger we can use the following commands to "walk through" the function
n to move to the "next line"
s to move to the "next line", or if the current call is a function, "step" into the function (debug this function)
f to move forward until the next break (with the added utility that if you are in a loop you stop at "loop end".
c to move until next break point or end of function (without breaking at end of loops).
Q to exit immediately
It you click n for example you will see
debug at #2: x <- x^2
printed in the console. This indicates the line that is executed next. Notice the value of x in the environment and run n again, notice the value changed from x | 3 to x | 9 and
debug at #3: x
is printed. This being the last line pressing n again will exit the function and print
exiting from: f(x)
[1] 9
Once you're done debugging you can run undebug(f) to remove the breakpoint and stop the debugger from activating.
This is a simple function, easy to debug, but the idea for more complex functions are the same. If you are in a long loop you can use f to skip to the end of the loop, similar to pressing n a bunch of times. Note that if you hit an error at any point it will exit automatically when the error occurs and you'll have to walk back to the point again or alternatively use browser.
In the case where you have a function like
f2 <- function(x){
x <- x + 2
f(x)
}
you can further step into the nested function call f(x) by using the s command while the line is printing
debug at #3: f(x)
or by using debug(f2) and debug(f) in conjunction. Both will give the same result (try it out!).
Now in many cases you might hit a bug or debug many lines (potentially thousands). In this case, you might have some "idea" where you want to start, and this might not be the start of the function. In this case you can use browser(). This basically sets a breakpoint. Whenever browser() is hit, it will stop and start the debugger (similar to debug(f) and calling f(x) but at a specific point). Try for example
f3 <- function(x){
x1 <- f(x)
browser()
x2 <- f2(x)
c(x1, x2)
}
f3(x)
and you'll notice see
Called from: f3(x)
printed (if you have run undebug(f2) and undebug(f) first).
Lets say it is not your function but a function within a namespace, well then we can even add the breakpoint ourself at run-time. Try for example calling
trace(f3, edit = TRUE)
and you will see an editing window pop up. Simply add browser() at the desired spot and click save. This edits the function within the namespace. It will be reverted once R is closed or alternatively you can remove it with another call to trace(f3, edit = TRUE).