Skip to content
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

Wishlist: delete_method #20048

Closed
timholy opened this issue Jan 15, 2017 · 11 comments
Closed

Wishlist: delete_method #20048

timholy opened this issue Jan 15, 2017 · 11 comments

Comments

@timholy
Copy link
Member

timholy commented Jan 15, 2017

With #265 fixed, there has been a substantial drop in the number of reasons to restart a julia session. One of the remaining reasons, changing a type definition, seems hard to fix. However, another reason (one that might be pretty easy now?) is to remove a method specialization that affects dispatch. For example, let's say I've defined

foo(x) = bar(x)
bar(x) = 1
bar(x::Int) = 2

If later I decide I didn't need/want that specialization bar(x::Int), it seems my only choices are to (1) define it to have the same code as the generic bar method, or (2) restart julia.

@tkelman
Copy link
Contributor

tkelman commented Jan 15, 2017

wonder if this is implementable by setting the max world age for that method to be very low?

@vtjnash
Copy link
Member

vtjnash commented Jan 15, 2017

It's probably conceptually similar. However, I currently only tracked additive edges. To delete something, you have to track the deletion edges also. I didn't do that already since I think it would make adding methods and ambiguity detection more expensive (these are the main costs of incremental deserialization). It might be possible for someone to backload more of the work onto the actual deletion to make this feasible.

@timholy
Copy link
Member Author

timholy commented Jul 15, 2017

What if there were an @uncallable annotation similar to @nospecialize? Revise could look for deleted methods in the source (trivial change), and if found re-eval with an @uncallable in front of them. Then (with a couple of extensions) you could reuse the current additive-edges framework?

@cstjean
Copy link
Contributor

cstjean commented Jul 15, 2017

Even without any change in Base, we could replace the deleted method with an invoke to the right remaining method, or throw a MethodError if there isn't any...

@timholy
Copy link
Member Author

timholy commented Jul 15, 2017

Not sure that would work. What if you regret introducing a specialization but have multiple fallbacks, which one should you call? You'd have to do a lot of julia-code reflection to determine how to write that method. Conversely, if we introduce an @uncallable then the type-system is in charge of performing the necessary reflection (as it does anyway).

@cstjean
Copy link
Contributor

cstjean commented Jul 15, 2017

What if you regret introducing a specialization but have multiple fallbacks, which one should you call?

Create a dummy function with all methods of f except the ones that were deleted, then use which to find what to call? But I agree that fixing Base somehow would be a lot better.

@timholy
Copy link
Member Author

timholy commented Oct 13, 2017

@vtjnash, I looked into this a bit more. Is there anything wrong with this?

# Notes on deleting functions
using Base.Test

# Example in https://github.com/JuliaLang/julia/issues/20048
foo(x) = bar(x)
bar(x) = 1
bar(x::Int) = 2

@test bar(7) == 2
@test foo(7) == 2

# We're going to delete the bar(::Int) specialization.
# Figure out who calls it. In this demo, I haven't gotten around to *using* this information yet,
# so this is just to show it can be done.
callers = Set{Method}()
for c in methods(bar)
    isdefined(c, :specializations) || continue
    tme = c.specializations
    isa(tme, TypeMapEntry) || continue
    mi = tme.func
    for b in mi.backedges
        push!(callers, b.def)
    end
end

# Now delete the bar(x::Int) method
ml = methods(bar)
mt = ml.mt
mt.cache = nothing  # recompile all. TODO: drop only the TypeMapEntry for the method we're deleting
# The method we want to delete is the most specific, so it's first. Drop it.
mt.defs = mt.defs.next
# Delete the first signature
deleteat!(ml.ms, 1)

@test bar(7) == 1

# TODO: invalidate the appropriate methods in `callers`
@test foo(7) == 1

@timholy
Copy link
Member Author

timholy commented Oct 21, 2017

Closed by timholy/Revise.jl#57

@timholy timholy closed this as completed Oct 21, 2017
@djsegal
Copy link

djsegal commented Nov 26, 2017

@timholy is your post two posts up still an acceptable way to do it? (like how revise does it)

just wondering before i check something into julz


edit: i just used delete_method from the pull request above

@timholy
Copy link
Member Author

timholy commented Nov 27, 2017

As Jameson says, it's a bit broken and apparently risks crashing your session. It also doesn't yet handle TypeMapLevel.

I just skimmed Julz, and it seems like it's kind of like Pkg.generate. Out of curiosity, why would it need delete_method?

@djsegal
Copy link

djsegal commented Nov 27, 2017

Julz is a wrapper for Pkg.generate but also loads every file and gives you a little bit of a framework to work off of

// see: include_all_files and export_all_files (and the like)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants