(Disclaimer: I’m a newbie)
I wanted to learn how Revise work. I created a function within a module:
in a file called some_module.jl:
module myModule
function myFunc(name)
println("Hello ",name)
end
end
In a different file, I import that with Revise:
using Revise
includet("some_module.jl")
import .myModule
printstyled("doing some work\n"; color = :blue)
printstyled("doing some more work\n"; color = :light_magenta)
printstyled("taking a nap\n"; color = :green)
printstyled("doing some more work\n"; color = :light_blue)
myModule.myFunc("world")
I can see that after I run this entire file in Atom/Juno I can call the function myFunc in REPL, and if I change the function definition in some_module.jl and save the file, then myFunc automatically reflects that change in REPL like magic and without even needing to re-include the file. Amazing.
However, I tried to repeat the experiment in Juno debugger, so I put a breakpoint, stopped in debugger, and tried to change myFunc again, but now the change does not reflect at all. That is until I stop the debugger.
I think it would be great to have this functionality mid-debugging. Am I doing something wrong or is this the expected behaviour?
It might be possible for the debugger to behave like you expect it to, but I think that’d require a lot of work. Revising methods you’ve not entered yet seems like a much easier task though – potentially just a matter of calling revise at the right time, but I’m a bit fuzzy on JuliaInterpreters internals atm. Maybe @tim.holy or @kristoffer.carlsson can chime in.
By default, Revise does its magic automatically when you’re about to execute a command entered in the REPL. In other words, you can make many changes to many source files, and Revise will detect each of them right as it happens. But changes are not processed yet. When you enter a new expression in the REPL, Revise processes all changes at once, before letting the expression be evaluated as usual (but with updated code).
I don’t think Revise is set up to have the same behavior with the Debugger REPL, which is why changes in the code are not accounted for until you exit the Debugger and hit the regular REPL again. But Revise still detects source code updates in the background, so what you can do is manually trigger the “revision” process using Revise.revise()
For example, given this source file:
foo() = bar()
bar() = 1
Here would be the transcript of an interactive, Revise-enabled debugging session:
julia> using Revise, Debugger
julia> includet("mysourcefile.jl")
julia> foo()
1
julia> @enter foo()
In foo() at /tmp/essai.jl:1
>1 foo() = bar()
About to run: (bar)()
# At this point, modify the definition of bar in the source file.
# The following prompt appears when you press ` (<backtick>)
# it gives you the possibility to call Revise.revise() explicitly
1|julia> revise()
# You go back to the regular debugger prompt typing <backspace>
# at the beginning of the line, and can resume your debugging session
# with (I think) all changes accounted for in functions that you've
# not started to debug yet.
1|debug> c
2
EDIT: as @pfitzseb said, this will not allow you to change a function while you’re debugging it. Only to change functions that you’ve not started debugging yet
Glad you like Revise! Regarding your request, unfortunately it would be impossible to implement this in full generality. If you’re midway through stepping through a method, and you revise its code, what does “continue where I left off” even mean? Internal variables might disappear, the entire algorithm might change, etc.
It would be conceivable to make this work for callees of the methods you’re stepping through, though; it could be as simple as having the debugger call revise() before executing each button press. I wouldn’t want to ship this without extensive testing, though, it is a bit scary.
While doing some tests about this, I noticed that we already have a few quirks in such situations, especially regarding the consistency between displayed and executed versions of the source code. Here is an example:
julia> using Revise, Debugger
julia> includet("essai.jl")
julia> @enter foo()
In foo() at /tmp/essai.jl:2
1 function foo()
>2 println("in foo")
3 return 1
4 end
About to run: (println)("in foo")
# Modify the source code at this stage:
# the new code is displayed, but the old code gets executed
1|debug> n
in foo
In foo() at /tmp/essai.jl:2
1 function foo()
2 println("in foo")
>3 return 2
4 end
About to run: return 1
1|debug> n
1
Should Revise get somehow disabled while the debugger is running?
Yes, that does seem bad. My presumption is that internally the code being debugged is still the original, but that the display is affected. We should probably grab a snapshot of the source-text when it is first entered and use that throughout.
That’s a good point. My particular use case, which I’ve stumbled onto a lot in Python, is debugging a code that does some long and heavy pre-processing and then at some point the output of that long procedure goes into another function, that has some bug in it.
Sometimes the nature of the bug is “empirical” in the sense that it is much easier to just empirically test the 2-4 possible configurations and see which one works instead of working out exactly what needs to be there. An example of what I would call empirical bug in Python: plot(contour[:,0], contour[:,1]) vs. plot(contour[:,1], contour[:,0])
It’s much easier to see the resulting contour overlayed on the image and know right away which is right than calculating how rows and columns in an image correspond to the vertical and horizontal axes in your contour generating code.
To do that you have to be able to stop in the debugger and call the plotting function, and if it doesn’t look right, change it and call it again.
So in this scenario, you are not changing the code that you are stepping through, only code that is imported as a whole from an external module. Thus excluding Revise from the file that you are stepping through might be the distinction to prevent the ambiguity.
I might be over-simplifying it and not really considering all the possible variations, but at least for me, this kind of use case is common and worthwhile to think about.