Developing a module the Julian way such that its functions are visible during development

I’m writing a Julia package that contains one module. The module includes many source files and functions. I want to develop new functions in the module without having to export every required function within the module so that they are visible to the new functions. In other words, I’d like the module functions to be in the namespace so I don’t have to qualify them or export each one as I develop. What’s the Julian way to do this?

I would get rid of the module when developing. Then, once I’m happy with the framework, protect everything with a module, and export public interface.

2 Likes

And this applies even when developing a Julia package? All the tutorials suggest creating a module called YourPackageName and then including files within the module. So you’re suggesting omitting the module from the package until some public interface is converged. Then wrap all includes with the module. Are there any side effects to this approach?

Actually, I personally never do this. It does make certain preliminary design decisions easier, but I always develop my packages as you outlined it. I don’t really see any disadvantage to it. But since you asked: one option is to expose everything initially.

Ok, I’ll try this mode of development and see if there are gotchas. Maybe some other Julia developers have something idiomatic? In general, I find the module/package/project system confusing right now, especially with respect to establishing a rapid development paradigm. Is there some reference to suggest best practices?

So do I get it right, that you want to have a module imported using using but develop new functions for it in a separate script? That’s why you want all the functions from the module available?

I find that editing the files and functions in the module as the best way to develop.
So what I do:
Create a folder for the module and the basic files.
Create an environment in a separate folder, dev the module into this environment.
Open in the editor files from the module - write functions etc. While in the other folder I have a file where I run the code. Everything is kept up to date, because I have Revise package installed that reloads a module every time the code changes.

So I am not sure if this is what you mean by rapid development, but everything works with almost no lag.

2 Likes

The functions within the module all “see” each other. You mean testing them in the REPL?

I usually do MyModule.myfunction(...) to use them while testing. A minor annoyance IMO.

In the upcoming Julia 1.9 release you can change the contextual module in the REPL:

$ cat src/TestPackage.jl
module TestPackage

function hello()
    println("hello, world")
end

end # module

$ julia-master -q
julia> using REPL, TestPackage

julia> hello()
ERROR: UndefVarError: hello not defined

julia> REPL.activate(TestPackage)

(TestPackage) julia> hello()
hello, world
15 Likes

That’s nirvana. When is it expected to drop?

Until Julia 1.9 arrives, I will adopt this method. I find it awkward since I have to prefix my development code that relies on module dependencies that are not exported with the module name. This is the case even if the function I am developing is intended to be within the namespace of the module since I’m testing code snippets of the function in the REPL, which is not in the namespace of the module. I believe the context change will be a big benefit for this style of development, which I’m used to in Python and MATLAB.

feature freeze is tomorrow. we won’t know exactly when the release will be (since it depends on what bugs are found) but it should be in the 1-6 month timeframe.

I usually also do MM = MyModule; MM.myfunction(...) to still reduce the annoyance.

Does Revise.jl invalidate the alias MM somehow?

Or

using MyModule
myfunction = MyModule.myfunction

?

1 Like

No, it doesn’t

Well, sometimes I also do it this way. You can also (temporarily) export myfunction . Just if MyModule_Has_A_LongName and I wish to have an access to all it’s functions, I’m saving a couple of keystrokes by aliasing to a short form.

I use this pretty frequently

function exportall(mod)
    for n in names(mod, all = true)
        if Base.isidentifier(n) && n ∉ (Symbol(mod), :eval)
            @eval mod export $n
        end
    end
end
5 Likes

Using your code snippet in combination with the suggestions of @mkoculak allows me to develop in the REPL (using Revise.jl) with nearly a seamless experience–something similar to MATLAB and python. I look forward to the context-aware v.1.9 to replicate this without brute force exporting all functions during development. Thanks for the responses. I’m happy with my dev environment now.

1 Like

My trick is that I tend to put my entire module body in a file named “__assembly.jl” and then put the include statement in my module

module MyModule
    include(joinpath(@__DIR__, "__assembly.jl"))
    export <<all my exported functions here>>
end

When developing, I simply run “__assembly.jl”, otherwise, I run “MyModule.jl”

2 Likes