-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Debugger / explorer - plan #520
Comments
Looking forward to it. |
Wouldn't it be great if Unison let you really see what your code is doing? So it feels like you're holding the code in your hands and watching it run? Rather than how it is with trace statements and the REPL - which feel more like taking measurements about your code from behind a lead-lined screen. Outline of this post...
What tools do we have today?What tools do we have for understanding why a program produces the result it does? Here are the ones that are commonly used with functional code.
These have some major shortcomings.
What do we want instead?What we want is a way of just seeing what your code is doing, in a live, hands-on way. We want our environment for exploring code execution to have the following properties.
Wouldn't it be great if Unison had something like this? Here's a sketch of what it might look like. I'm assuming a console-based UI, rather than anything more flashy. Sketch of a Unison execution explorerLet's fire up the explorer on some of the fuzzy string matcher code from Paul's recent blog post - we type this at the Unison prompt. That drops us into the following view of the Notice how we're straight away actually looking at our code. And right underneath it we have some values, including the overall value of the outer expression we're exploring, and the function arguments, in this case A bunch of the code is shown in blue, and this part, the 'focussed expression', also has its value displayed. Let's use the arrow keys to zoom the focus inwards to part of the syntax tree, and end up with the following: Now, Those asterisks on the left of the code now start to make sense: they're showing the lines that are actually hit during execution. We're hitting the Just above the code, it says We've got a few more expressions being displayed for us too - both function arguments, and the variables brought into scope within the match expression in the case block. The function value Notice how the type of
Actually, in the source, it's
but the type variables are known since we're in the context of the call that Let's now focus on the expression The first line has changed from
to
We can jump between our bookmarks, either by typing the bookmark number, or by using the arrow keys to select the So, so far we've seen the 'outer' (first) invocation of Notice how we're now on
We still have the same expression focussed, but its value has changed, to Now let's skip all the way to the fifth and last invocation. Noticed how an asterisk has moved, showing that this time we're hitting the Let's say we want to understand why this is the last invocation, so we focus on the And then we hit enter to explore inside it. Now let's focus on the Now we understand that we're down to only one character left from the end of the original input string - we can see that Here's the transcript that gets dumped to file. This is a list of watch expressions, but with some exotic-looking qualifications separated by What happens if we load these watch expressions again at the Unison prompt?
We can come back to those watch expressions and re-evaluate them in the normal way, even as we change the code. Some changes (like if we get rid of the call to We can also load that transcript up using the explorer, and it will bring us back to here. Execution paths and scope pathsThere are two mini-languages being proposed here: for execution paths like Execution paths seem (to me) clearly useful things - the explorer will need to have them at least internally, so why not surface them so the user has a way of thinking and talking about a point in the execution as well. I'm sure that implementing them would be significant work in the interpreter, for it to be able to track its progress through one of these paths, but it seems like the sort of concern it's legitimate to make an interpreter deal with. You could imagine doing more with them:
Scope paths on the other hand, like Various comments
EvaluationIn some ways, this is just like the standard imperative debugger experience you get in Visual Studio, albeit through the grainy lens of a console window! You see the code in front of you, and you can see the values of variables, evaluate expressions, and you go up and down the stack. But it's worth highlighting some interesting extras in this proposed Unison experience.
Precedent / related workI'll confess I haven't taken much time to look around at what's already out there yet - here's a quick skim from googling. I was looking for stuff that played with the fundamentals, but mostly the more developed end of what's out there at the moment seems to just give you the VB6 experience: the code is in front of you, you have a watch window, you can see the stack frames, you can step forwards (only), you can run to a breakpoint. Probably there's interesting stuff in the academic literature from decades past - if you have links please reply with them! Here's the not-so-standard stuff I did find: For Haskell, Hat can do some neat stuff.
I learn from the bottom of this page that Hat hasn't kept pace with the Haskell language, so is generally not useful now. This strengthens my suspicion that the debugger deserves a place in amongst the core Unison tooling. Elm has record/replay, with a (currently basic) state timeline viewer for replay. lamdu gives you runtime values overlayed much closer over the code - you can see the value taken by each subexpression. More standard offeringsRacket has a GUI debugger, screenshot on this page. Purescript currently has a barebones console debugger where you have to instrument your code with the places you want to add breakpoints. Swift lets you set breakpoints from its REPL. Imperative .NET languages have the Visual Studio debugger, which basically gives the classic VB6 experience, with lots of polish. I'm not sure about the situation with Scala debuggers. scala-debugger.org seems to be about setting breakpoints by instrumenting the code. Maybe it's meant to be used via Eclipse. IntelliJ seems to be smoother - VB6 style. gdb and lldb exist! I'm not sure about lldb, but gdb lets you step through execution, set breakpoints, and read variables. These tools are geared towards debugging compiled binaries. gdb lets you load and inspect the memory image of a crashed process. |
This is great! I really like the ideas here and would like to see this kind of thing happen in Unison for sure. |
Ideas for visualising expression evaluation: https://www.columnal.xyz/design-blog/debugging-display |
Here's some more prior art from Bret Victor: https://youtu.be/8QiPFmIMxFc?t=984 |
I'm getting excited about this idea again! I'm planning to start work on it. In a nutshell, the plan is to implement the 'explorer': a terminal-based replay debugger, that (a) surfaces the actual code in the UI, and (b) allows you to save/load debugging sessions. I've been thinking about how to break it down into phases, each delivering something useful:
Plus you can imagine a bunch of further enhancements that would make sense on top of this central stuff (like the save/load session thing). These phases are explained in more depth in the sections below. I'm thinking my progress on this will be measured in years rather than months, but I reckon I'll get started anyway. If someone reading this wants to get involved and take on some pieces, that would be cool! Would be fun to collaborate on it. Get in touch with me on slack if you might be interested. I'm not suggesting/expecting the core team to get involved with implementation - I think they should stick to their other priorities. But for sure I will need their ideas and help/review with design work. QuestionsHere are the key questions where I need input/blessing from the core team. Arya and Runar are already on this thread; paging @pchiusano too.
TracingLet's have an option to get the Rt1 runtime to emit tracing of function calls and variable bindings, to a file. The output might look like the following. This is a mock-up of how it would look from the example in the post above. In the trace, you can see a stack depth counter on the left. Each line is showing a couple of levels of the stack, plus information like argument values, return values, and which match was chosen within a case expression. (Maybe it could be improved with some tabulation/alignment.)
This proves out a bunch of machinery that's needed in the interpreter for the full explorer, doesn't require a lot of extra work on top, and delivers something pretty useful. No more debugging with trace statements! 😎 Key bits of work needed:
Interesting issues:
Code explorerLet's have an interactive terminal-based code browser embedded in It might look like the following. Arrow keys navigate which function call is highlighted (blue) - enter jumps to showing that function. (The 'e' option to edit is equivalent to running the ucm edit command - it prepends the function to a scratch file.) This is independent of tracing support. This builds up the terminal infrastructure needed for the full explorer. It might also be handy to have a terminal-based browser for a local codebase, even if that ends up duplicating a browser-based one someday. The code explorer is a bit like the 'offline mode' for the full explorer: you can browse the code, but it's not actually running. Key bits of work needed:
Interesting issues:
Run to breakpointLet's run the interpreter til it hits the user's chosen breakpoint (execution path), freeze it, then allow the user to inspect the functions that are in the stack using the code explorer, and see values of arguments and variables. This takes the IR annotations and interpreter instrumentation from the tracing phase, and brings it together with the code explorer into something just about recognisable as a barebones debugger. Key bits of work needed:
Interesting issues:
Play/rewindLet's allow the user to wind program execution forwards and backwards, either step-by-step or to the next breakpoint. Rewind will use a cache of snapshots of interpreter/program state - actual evaluation only ever runs forwards. We can simulate going backwards to time N by going forwards from a snapshot from time M, where M<N. Key bits of work needed:
Interesting issues:
Further enhancements
|
I missed a couple of points on the question of doing this in Rt1 vs Rt2. In favour of maintaining two runtimes, one for debug one for speed:
But on the other hand:
Observation:
|
It would be cool with a debugger with a bunch of built in visualizations for the data plus the ability to create new visualizations, for example for new data structures. Then you could get the debugger to create visualizations like these while implementing for example a new algorithm |
This issue tracks the overall plan and progress on implementing the unison explorer.
Here are links to the key posts from the trail below.
---- [original text follows] ----
I've been playing with an idea for a debugger (or 'explorer') for Unison, and wanted a place to post it for discussion.
Feel free anyone to post reactions or other ideas!
Don't expect this to get much attention from the core team anytime soon though - this would be a major project, and there are already a few of those on the go. Maybe this is something for another year!
Post to follow, below...
The text was updated successfully, but these errors were encountered: