Is there a mechanism in Julia to load modules on demand?

As I understand, using and import statements must be at the top level. However, sometimes I just want to import a module on-demand, in a function that uses that module. For example, a large module X contains both functions for computation and functions for plotting in a certain scientific domain. So X imports a bunch of other modules, including Plots.jl, at the top level, which slows down the import of X significantly (via using X or import X), even though I usually only need its computation functions, not its plotting functions. Is there a canonical way in Julia to import Plots.jl only when the first plotting function is called, but not before? Certainly one way is to split X into, e.g., X and Xplots, where all the plotting functions are in Xplots. But apparently the developers of X may find this method undesirable and potentially break a lot of existing code that uses X.

I’ve tried a few things. The following caused an error because using must be at the top level.

test1(x, y) = x*y

function test2(x, y)
		 using Plots
		 plot(x, y)
end

The following didn’t cause an error at first, but a run-time error:

test1(x, y) = x*y

function test2(x, y)
		 @eval Main begin
		 	   using Plots
		 end
		 plot(x, y)
end

Calling test2(0:0.1:1, sin) resulted in an error:

ERROR: MethodError: no method matching plot(::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, ::typeof(sin))
The applicable method may be too new: running in world age 26765, while current world is 26789.

I guess Plots.jl may implement some tricks that allow it to load backend modules on demand, so there may be a way. However, is it cleaner to allow importing modules at levels other than the top level, says in a function’s scope? Or will there be problems if this is allowed?

Probably someone with more expertise will give you better answer to your exact question. From my point of view problem is somewhere else: you should move your calculation functions to a separate module that does not depend on Plots.jl. Then you may ship plotting functions as other module that depends on your module, or maybe as just .jl script.

This doesn’t really save you any time though right? Conditional on plotting something, you still face the same amount of “waiting”. You are just shifting when some of that waiting happens, and also it seems making the code more fragile.

@tbeason I think plotting is not used in most of the cases

The Requires package effectively addresses this. You would need to refactor things to put the optional logic at the top level (see the docs).

4 Likes

eval executes in global scope, not in the local scope of your function. The scope of test2 was created before you imported the Plot module into global scope. Thus it does not know the method.

You could use then Base.invokelatest to call the plot function like that:

function test2(x, y)
		 @eval Main begin
		 	   using Plots
		 end
		 Base.invokelatest(plot, x, y)
end

and it will do what you want.

The package Requires.jl mentioned above is one way.

For Plots specifically, you can investigate plot recipes, where your package depens only on RecipesBase.jl.

2 Likes