Manually reloading a module in 1.1?

How do I manually reload a module in Julia 1.1? (I’m on 1.1.1 to be specific)

I’m aware that Revise.jl exists, but it does have its known limitations and I’ve also seen issues and crashes together with Jupyter. These issues should of course be investigated separately. However, I figured there just should be some way to reload a module manually if anything else fails.

1 Like

Just re-include the file that defines the module, or evalute the module definition manually.

1 Like

Ok, that sounds easy. Thanks!

A follow-up question: Say I have defined my own little module MyMod and I’ve configured my LOAD_PATH so that I can just say using MyMod. Is there now any way to get the defining file from the module object? In this case, I could write my own reload(module_object) = include(defining_file(module_object)) function without having to remember where I put that code.

FWIW, I just wrote my little routine in my startup.jl that tries to do a reasonable job at loading the right file. It’s enough for me, but probably doesn’t cover all of Julia’s package system.

"""
    reloadfj("MyModule")
    reloadfj(:MyModule)

Reload-find-in-julia. Searches load_path() for likely locations of a <mod>.jl file and re-include()s it, which is apparently how you manually reload stuff in Julia.
"""
function reloadfj(mod::String)
    lp = Base.load_path()
    for s in lp
        d, f = splitdir(s)
        if f == "Project.toml"
            filedir = joinpath(d, "src")
        else
            filedir = s
        end
        f = joinpath(filedir, "$mod.jl")
        if isfile(f)
            include(f)
            return # Only reload once if we happen have the same dir twice in the LOAD_PATH
        end
    end
    @error "Module '$mod' not found in LOAD_PATH" mod expanded_load_path=lp
end
reloadfj(mod::Symbol) = reloadfj(string(mod))

@pfitzseb, I just found another issue. Turns out, re-including doesn’t update the exported identifiers in scope.

Say I changed a function myfun in MyMod. Then:

> include("MyMod.jl"); using .MyMod
WARNING: replacing module MyMod.
WARNING: using MyMod.myfun in module Main conflicts with an existing identifier.

This leaves two versions of myfun in scope: The new one, as MyMod.myfun and the old one, as myfun. This is a problem, because my code (Jupyter cells / REPL history) mentions the unqualified name.

So to even be able to reload anything, I’d always have to use the qualified name from the beginning. There must be a better way!

You might be interested in Revise.jl

2 Likes

Given what I’ve written in my original question, I would’ve appreciated a bit more than a single-package answer here.

I noticed that Revise exposes a revise(mod::Module) function to manually reload a module and one can deactivate automatic reloading via ENV["JULIA_REVISE"] = "manual". I’ll try that now.

Having said that, Revise messes with many Julia internals (just look at __init__()!) to get a lot of features (like truely inremental reload). I was hoping for something much simpler and (thus) more stable. Re-including everything doesn’t do the trick.

I’m surprised, frankly, that no such “plain & simple” reloading functionality is included in the language. AFAIS, it shouldn’t be that hard: For every identifier, keep track of the module that defines it and if the user asks to reload that module, just delete it before reloading. I guess there are some intricacies with, say, methods of the same function defined in different modules, but I don’t see anything that should fundamentally prohibit this feature and that is more important than having simple, robust reloading.

Apologies, was on my phone this morning and didn’t properly read through your OP, just saw in the last post that you had issues with redefining functions in modules, which is why Revise.jl seemed like an obvious thing to use!

Use import rather than using and scope every call (e.g., MyMod.myfun(x) rather than myfun(x)). Yes, that’s more verbose, but it’s the only “plain & simple” approach there is. Once types get defined (and each function has a type), Julia can’t undefine them; what do you do with old objects of those old types? Everything must have a type, and once a name means one thing it can’t mean another.

Just wishing this were a simple problem doesn’t make it so—Revise is complicated because it’s a complicated problem. It should be pretty reliable and stable, so if it’s not please do report issues.

3 Likes

AFAIK this problem is in principle solvable (Common Lisp did it), it “just” requires a lot of work and is not a priority at the moment.

But it would be awesome. :wink:

BTW, do you think that

is still a viable approach?

What does Lisp do with objects created at the REPL? E.g., typeof(a). Those objects may not have the data needed to “convert” them to a new type.

Not easily. I think it’s still worth thinking about, but probably an easier solution would be to define new gensymmed type names and have some sort of autoreplacement. A sketch might be

Tnew = gensym(Told.name.name)
mths = methodswith(Told)
for m in mths
    Core.eval(m.mod, replacesym!(definition(m), Told=>Tnew)
    Base.delete_method(m)
end
Core.eval(mod_of_struct, replacesym!(struct_expr, Told=>Tnew))

together with a call to replacesym! in Revise’s REPL loop. Would make a great PR for someone interested in learning a bit more about Revise’s internals!

IIRC you’ll get some kind of error if the layout changed and you’re using the old object, but I don’t remember it actually segfaulting or anything. It was very convenient, although the problem may be more complicated in Julia.

I don’t think you get an error: new slots are just added and old slots deleted, and then you can, in the typical Lisp manner of course, customize the whole thing. Thinking about this, maybe this is overkill for Julia.

What I would consider a reasonable solution for Julia is have the old types available, but make the name refer to the new type after redefinition. Eg in

struct Foo end
bar(x::Foo) = typeof(x)
B = bar(Foo())

if the type is redefined as

struct Foo
    a::Int
end

then bar is recompiled at some point before it is called next time, as then

B !=== bar(Foo())

and the old type would still be available, you just would not be able to instantiate it and it would print differently, eg

julia> B
Foo [redefined at world age ...]

Maybe there is a wrinkle I am not seeing though.

Right, that’s basically the solution I’m hoping for too. The blocker is https://github.com/JuliaLang/julia/pull/22721#issuecomment-315564890.

1 Like