-
Notifications
You must be signed in to change notification settings - Fork 111
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
Revise does not apply functions in the two-argument form of include
#634
Comments
Is this an issue that seems easily fixable or is this a tougher nut to crack? It would be really nice to be able to use the 2-argument |
It's not trivial, but not impossible either. Someone would need to modify Revise's internal data structures to keep track of a |
We wanted to use this feature of |
Friendly ping on this; would you be able to add this @timholy? Seems like it's the 2nd-most requested feature (and of those top two, perhaps requires the least changes?) It seems like this might be the reason that Revise.jl doesn't work with DispatchDoctor.jl in #820? |
I tried to get started on this by modifying Line 79 in f4edc99
to be Dict{String,Tuple{WatchList,Union{Function,Nothing}}}() and then Line 889 in f4edc99
mapexpr::Union{Function,Nothing}=nothing argument. And likewise for includet
However I quickly got lost. @timholy I think you might need to help with this one... Sorry I couldn't be more useful! Would be really nice to have this for compatibility with DispatchDoctor.jl as apparently the precompile times become slower with it. |
I don't have time for general Julia development very often these days. This is a bigger project than I can tackle currently. |
@timholy perhaps you could simply point out specifically what needs to be changed for this to work? |
It's not so obvious that I can confidently predict it. You want to grab the expression that gets produced by |
@MilesCranmer i would be willing to help if possible in trying to achieve this, I have been trying to understand better revise internals for using it in a package of mine and I would love to have this functionality as well. That being said, I believe an additional challenge is that the specific mapexpr to use is not easy to extract as it's not known by the In any case we could have a chat if you are interested in trying to collaborate on this (I'll also be at juliacon in Eindhoven to meet in person if you'll attend) |
Yeah, you'll need to store the |
@disberd that’s great! I won’t be at juliacon unfortunately but good to hear you are interested in fixing this. (I don’t think I have much time to actually work on this though, aside from some tiny/quick contributions) @timholy This sounds like the “proper” way to do it, but is there a way I can just hack it in? Let’s say I don’t care about having the smallest possible eval. Say I was happy to evaluate all the included code every single time any change is detected. I ask because right now it throws an error. But even if it was extremely inefficient, I think it would be still useful to just have something working. A useful hack is still useful. |
I can't imagine a hack that would work that doesn't do it this way. Here's the issue: module MyPackage
function mymapexpr(ex)
...
end
include(mymapexpr, "anotherfile.jl")
end Now revise |
Well one could always leave it as a regular eval(mymapexpr(quote
...
end)) But maybe there’s something in between this very manual approach and a full solution within Revise..? |
I'm not sure you need any changes to Revise for that. Worth testing. |
I think it would work since it’s just a regular eval at that point. But the downside is it’s no longer automatic. So I guess I’m wondering if Revise could just read any 2-arg But maybe this is exactly the same as the full solution… I’m not sure |
Right now I have a macro that acts on an entire library, and does so by converting I am just wondering if there's a simpler approach that does not require a full solution to this issue? |
I feel like this would require structural changes in Revise.jl because it seems like if you had the situation include(foo, "file.jl")
include(bar, "file.jl") Many assumptions made throughout Revise would break. For example Probably easier to find some hacky workaround for this at a per-instance level? Unless of course someone wants to fix it for real |
I won't commit hacks to Revise. It's hard enough to debug as it is. Better for it to be as correct as it can be, somewhat maintainable, and limited than "works for me but wow is this a mess." And yes, Revise assumes that each file gets included just once. But if you're adding a |
Sorry that's not what I meant. And by "hacky" I am mostly just being self-deprecating :-) It's actually a decent solution and more robust than what happens currently – What I meant was for Revise.jl to expose an interface for specifying mapexpr for specific files, similar to how it supports user-defined callbacks: Lines 18 to 24 in f4edc99
For example you could have const user_mapexpr_by_file = Dict{String, Function}() And just require the user to add their mapexpr manually if they wish to use the two-argument form. Then, within DispatchDoctor.jl, I can write a Revise.jl extension that will tag each "stabilized" file with the right mapexpr, rather than what I currently do which is modifying |
Here's what I don't like about that: there is a solution that keeps Revise "invisible" and makes it Just Work. Revise has gotten to where it is by being willing to do the hard work to support as much of Julia as it can as correctly as possible. Heck, I wrote an entire lowered-code interpreter (with a lot of help from some key people) just so I could reason about what is happening for the ~10% of functions that are defined via an If we break that tradition now, then there will be a release of Revise that requires package authors to distort their code to handle limitations of Revise---Revise will no longer be invisible. Then sometime later when someone puts in the real work, we say "now you can put your package code back the way you wanted it in the first place. And oh by the way, until you do that, your users can't use Revise 4.x, it's broken for those packages that used the hack on Revise 3.x." I'm not certain the latter will happen (it's probably less than a 50% chance), but it seems that it could be a risk. That's why I don't like taking shortcuts. Until now, I've added new capabilities to Revise when I was prepared to support them. |
@timholy I understand and agree with your intentions to maintain the integrity of Revise. However, I want to re-emphasize that the current behavior of Revise.jl with the two-argument Therefore, even a temporary, less elegant solution – like enabling authors to add their own workaround – is preferable to the current state, which silently changes the behavior of revised Julia code, without any workaround possible. An alternative, correct solution that aligns with your ethos could be to throw an error on any two-argument Thanks for considering this. |
We could add it to the limitations section of the documentation. Yes, it's wrong now. 2-arg include is a relatively late-breaking change and so niche it has never been a major priority before.
It's a good idea (I would make it a warning and not an error), but how would this work? Other than turning on file-watching, Revise only gets engaged when you save changes to a tracked file. Suppose you start editing the file that got 2-arg included; since the 2-arg I suspect the only solution is to parse the entire package. That is surely doable, but it makes Revise "heavier" in both speed and memory consumption. You have to defensively parse every file of every package that has been revised. (That's true even of a "proper" fix to this issue, I fear.) I like MilesCranmer/DispatchDoctor.jl#40 much better 😛. Or of course, me or someone sitting down for a week and implementing 2-arg include correctly. I'm not saying I won't merge a proper fix to this issue (to the contrary, subject to the caveat about "heaviness" above), but I'm putting my foot down when it comes to a dirty hack. I don't intend to change that decision. Some other alternatives are:
|
I think a warning should be prioritized for now if possible. For example, when using DispatchDoctor with Revise, I often forget that Revise removes the generated While my use case is fairly innocuous as its just a developer tool, others might use the two-argument include for larger codebases with more complex |
The main issue is, how does Revise issue this warning without defensively parsing the whole package? In other words, to catch something that is rarely used we have to make Revise clunkier. How do we decide what to do about that tradeoff? |
I should say I think one should be cautious about relying on usage frequency in open-source code for such decisions – I had an instance where a 100k-line closed-source Julia codebase at a company used my package and sent me some unexpected bug reports (for a different thing). I think basically that anything exported in the Julia standard library is fair game and should either be handled correctly or result in an error/warning. This is because we can’t measure what features are used in internal code, especially in industry settings where coding practices can differ significantly from open-source conventions. Niche Julia features, as long as they are documented and official, might be used extensively within internal codebases. So regarding the question of catching this, I'm not sure. I feel like the prudent thing to do would be to implement the slower/clunkier solution now, just to preserve correctness (especially since the type of changes performed with a 2-arg include may be very subtle and not immediately noticed). And then once JuliaLang/julia#54794 is fixed, it can go back to the more efficient approach. (And anybody complaining about slowness would be motivated to help us fix this 😉). But there's certainly a tradeoff here. |
Do keep in mind Revise is a developer-efficiency tool, not a compiler. What you're describing is a bit more appropriate for a compiler, where any failure to handle any language construct at all is quite catastrophic. That's why we have PkgEval for Julia development. In VSCode, would you prefer it if F12 resulted in a stacktrace if it couldn't find the source lines for the given call? Or if GitHub Copilot errored noisily if it wasn't sure about your intent? Even DispatchDoctor seems to be something you intend to be transient and quite selective, as you're presumably well aware that if you enforced it globally virtually nothing would run. But yes, it's probably OK to implement the slower thing here. Presumably the number of packages with edits in any given session is modest. |
Fair enough! I think perhaps Revise.jl might just be a victim of its own success a bit here :) in that it is so reliable and smooth, that people just have it on all the time (at least I do!) and so the expectations are artificially inflated a bit relative to other dev tools. But I totally get your point though, and I would agree we shouldn't hold it to the standard of a compiler.
Good point, yes this sounds reasonable to me too. |
The two-argument form
include(mapexpr::Function, path::AbstractString)
allows transforming the code in the file atpath
. However, Revise does not seem to apply the same transformationmapexpr
when re-loading code that isinclude
d using this form. For exampleThe expected output of the last line would be something like
Hence, this demonstrates that Revise did not apply the transformation
expr -> quote @fastmath begin $expr end end
passed toinclude
.The text was updated successfully, but these errors were encountered: