Tail Recursion and Debugging (2011)
funcall.blogspot.com
funcall.blogspot.com
Depending on the loop syntax, presumably you'd want to know (in the case of a for loop) what variables are initialized, what the test condition is and its result, and what is altered. But, in the case of C for loops as an example, this wouldn't be easy to automate since the user can always do something like:
for(;;) {
if(condition) break;
// other logic
}
In the best case (which would be the most data-ful case and hardest to use) you'd log all changes that occur within the loop. What you really want would be smaller, like detecting the break and the condition leading to it and watch that.More realistically, you probably want to document your loop invariants with assertions which could be optionally recorded in your trace output.
Once you have more lines than fit in a window, you've already got "many" and now it's time to talk about tooling like searching logs.
A million lines of log feels like an excessive waste intuitively, but that's only because I was brought up on 8-bit computers with 16K of RAM. Now my watch has more power than probably all of the computers I owned in the 20th century put together, and a single image it displays might have more bits than a million lines of a log file.
---
I know this stuff isn't free, but we should at least consider the possibility that what we really need are proper and comprehensive logs/traces, and a "stack trace" is nothing more than an abbreviated execution trace that just happens to be relatively cheap to collect when something goes wrong.
The MIT Scheme debugger has a notion of "reductions and subproblems" [1], which basically correspond to tail calls and non-tail calls. You can step "backwards" to the previous reduction, or "upwards" to the previous subproblem. In terms of other programming languages, that corresponds to going back to the previous loop iteration, or going up to the previous call frame.
Behind the scenes, this is implemented using a "ring-buffer or ring-buffers" [2], which stores the values of local variables for, e.g., the last 10 iterations through a loop. So if you step back further than that you will lose history and jump back to the start of the function call.
(The second link goes to the same blog as OP, I guess he has been writing a lot on this topic!)
[1] https://www.gnu.org/software/mit-scheme/documentation/stable... [2] http://funcall.blogspot.com/2009/05/you-knew-id-say-somethin...
In terms of debugging, tail-call elimination is no worse than the optimizations typically applied to loops.
That has always been a strange argument. You can turn features on and off, so the "it's hard to debug" argument doesn't make sense. Scala added @tailrec and I don't think I've ever heard complaints about it. Clojure has recur. Both are JVM languages.
> it is also the goal of this project to add an even lighter-weight construct that will allow unwinding the stack to some point and then invoke a method with given arguments (basically, a generalization of efficient tail-calls). We will call that feature unwind-and-invoke, or UAI. It is not the goal of this project to add an automatic tail-call optimization to the JVM.
Since this paragraph is explicit about the feature not being "automatic", it'll be interesting to see what form this takes.
[1] https://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.ht...
> at com.alba.vware.ManagedServletPipeline.service(ManagedServletPipeline.java:91)
> at com.alba.vware.FilterChainInvoke.doFilter(FilterChainInvoke.java:62)
> at com.alba.vware.FilterDefinition.doFilter(FilterDefinition.java:167)
> at com.alba.vware.FilterChainInvoke.doFilter(FilterChainInvoke.java:58)
> (82 repetitions of 2 lines elided) [sic]
> at com.alba.vware.FilterDefinition.doFilter(FilterDefinition.java:167)
> at com.alba.vware.FilterChainInvoke.doFilter(FilterChainInvoke.java:58)
> at com.alba.vware.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:118)
> at com.alba.vware.Filter.doFilter(Filter.java:113)
This is a tooling problem.Whether you want to see tail deleted frames or not depends a lot on how you code. I can appreciate both sides of the argument and haven’t ever really made up my own mind.
I remember debugging something similar on maven due to a circular dependency somehow that only broke my team's project;
ran mvnDebug, connecting jdb; stacktrace crapped out; dumping variables, and adding the appropriate <exclusion> worked :D
that was the highlight of my sunday evening.
https://github.com/apache/maven-resolver/blob/d1d30018b990f7...
(_could_ because I don't know how mathematically hard that is)