Wiping modules and persistent Base.show

I’d like to be able to overwrite an existing module within a session and have everything it defines dropped. This works fine for functions, structs, variable assignments but not for functions it extends from other modules and I’m not sure why.

To clarify what I’m after:

A = Module(:A)
include_string(A, "a = 5; foo() = 2")
isdefined(A, :a)   # true
isdefined(A, :foo) # true

A = Module(:A)
isdefined(A, :a)   # false
isdefined(A, :foo) # false

this is what I want.

But now if I do the same thing but extend Base.show in the first pass, this keeps on living even after calling Module again:

A = Module(:A)
include_string(A, raw"""
    Base.show(::MIME"text/html", i::Int) = "<span>Int: $i</span>"
    """)
A.show(MIME("text/html"), 1) # <span ... ok this is what I want initially

A = Module(:A)
A.show(MIME("text/html"), 1) # <span ... not ok, I'd like this to error

Is there a reason why that is? and assuming there’s one, is there a workaround?

Thanks!

In the latter case you are adding a new method to an existing function, not defining a new function. Methods in Julia have no notion of scope, a given function always refers to the same methods, regardless of from where it is called. This is actually a feature, because it’s one of the the main reason Julia’s multiple dispatch is so powerful for generic code.

For your particular use case you could perhaps use Base.delete_method, but note that this is an internal function, so it might break in future Julia versions:

julia> Base.show(io::IO, ::MIME"text/html", i::Int) = print(io, "<span>Int: $i</span>")

julia> repr(MIME("text/html"), 1)                             "<span>Int: 1</span>"

julia> Base.delete_method(which(show, (IO, MIME"text/html", Int)))

julia> repr(MIME("text/html"), 1)                             ERROR: MethodError: no method matching show(::IOBuffer, ::MIME{Symbol("text/html")}, ::Int64)
Closest candidates are:
  show(::IO, ::AbstractString, ::Any) at ~/.julia/juliaup/julia-1.7.0+0~aarch64/share/julia/base/multimedia.jl:111
  show(::IO, ::MIME{Symbol("text/plain")}, ::Any) at ~/.julia/juliaup/julia-1.7.0+0~aarch64/share/julia/base/multimedia.jl:47
  show(::IO, ::MIME{Symbol("text/csv")}, ::Any) at ~/.julia/juliaup/julia-1.7.0+0~aarch64/share/julia/stdlib/v1.7/DelimitedFiles/src/DelimitedFiles.jl:829
  ...
Stacktrace:
 [1] __binrepr(m::MIME{Symbol("text/html")}, x::Int64, context::Nothing)
   @ Base.Multimedia ./multimedia.jl:159
 [2] _textrepr(m::MIME{Symbol("text/html")}, x::Int64, context::Nothing)
   @ Base.Multimedia ./multimedia.jl:151
 [3] repr(m::MIME{Symbol("text/html")}, x::Int64; context::Nothing)
   @ Base.Multimedia ./multimedia.jl:147
 [4] repr(m::MIME{Symbol("text/html")}, x::Int64)
   @ Base.Multimedia ./multimedia.jl:147
 [5] top-level scope
   @ REPL[25]:1

Also keep in mind that you can’t revert to the previous definition if you completely overwrite a previously defined method. It’s also still type piracy, so it’s definitely possible that such workarounds result in weird bugs or even segfaults and you are likely to encounter excessive compile times due to method invalidatioms.

2 Likes