Optionally loading a module

suppose that a module foo.jl depends on external packages which take several seconds to load, e.g.

julia> tic(); using JuMP, GLPKMathProgInterface; toc()
elapsed time: 4.929913064 seconds

in my case, foo.jl only eventually uses JuMP, thus it seems more convenient to load it only when needed.

the strategy that i’ve used successfully with PyPlot is to evaluate the using command at run-time. however, it fails here. the problem appears with the @variable macro.

for example:

module foo

function my_fun(...)
    ...
    if isdefined(:JuMP)
        eval(Expr(:using, :JuMP))
        model = JuMP.Model(solver=GLPKSolverLP())
        JuMP.@variable(model, x[1:p.dim])  # << triggers: LoadError: UndefVarError: JuMP not defined
        ...
    else
        error("...")
    end
end 
end #module foo

is there a way to “avoid expanding the macro”? or, is there another workaround for optionally loading a package? Thanks

EDIT. here’s a MWE:

module foo

using JuMP
using GLPKMathProgInterface

function my_fun(C::Float64)::Float64
    m = Model(solver = GLPKSolverLP())
    @variable(m, 0 <= x <= 2)
    @variable(m, 0 <= y <= 30)

    @objective(m, Max, 5x + 3*y)
    @constraint(m, 1x + 5y <= C)

    print(m)

    status = solve(m)

    println("Objective value: ", getobjectivevalue(m))
    println("x = ", getvalue(x))
    println("y = ", getvalue(y))
    return getobjectivevalue(m)
end
export my_fun
end # module foo.jl

and:

julia> tic(); include("foo.jl"); using foo; toc()
elapsed time: 6.753355159 seconds
6.753355159
 
julia> my_fun(3.0)
Max 5 x + 3 y
Subject to

 x + 5 y ≤ 3
 0 ≤ x ≤ 2
 0 ≤ y ≤ 30
Objective value: 10.6
x = 2.0
y = 0.2
10.6

now let’s try the package Requires.jl :

module bar
using Requires

function my_fun(C::Float64)::Float64
    !isdefined(:JuMP) ? eval(:(using JuMP)) : nothing
    !isdefined(:GLPKMathProgInterface) ? eval(:(using GLPKMathProgInterface)) : nothing
    @require JuMP begin
        @require GLPKMathProgInterface begin
            m = Model(solver = GLPKSolverLP())
        end
    @variable(m, 0 <= x <= 2)
    @variable(m, 0 <= y <= 30)

    @objective(m, Max, 5x + 3*y)
    @constraint(m, 1x + 5y <= C)

    print(m)

    status = solve(m)

    println("Objective value: ", getobjectivevalue(m))
    println("x = ", getvalue(x))
    println("y = ", getvalue(y))
    return getobjectivevalue(m)
    end
end
export my_fun
end # module bar.jl

then

julia> tic(); include("bar.jl"); using bar; toc()
elapsed time: 0.087633098 seconds
0.087633098

julia> my_fun(3.0)
WARNING: Error requiring JuMP from bar:
UndefVarError: C not defined

Stacktrace:
 [1] err(::bar.##7#15, ::Module, ::String) at /Users/forets/.julia/v0.6/Requires/src/require.jl:42
 [2] withpath(::bar.##6#14, ::String) at /Users/forets/.julia/v0.6/Requires/src/require.jl:32
 [3] listenmod(::bar.##5#13, ::Symbol) at /Users/forets/.julia/v0.6/Requires/src/require.jl:13
 [4] macro expansion at /Users/forets/.julia/v0.6/Requires/src/require.jl:52 [inlined]
 [5] my_fun(::Float64) at /Users/forets/Projects/Compositional/CompositionalHA.jl/test/Sets/bar.jl:7
 [6] eval(::Module, ::Any) at ./boot.jl:235
 [7] eval_user_input(::Any, ::Base.REPL.REPLBackend) at ./REPL.jl:66
 [8] macro expansion at ./REPL.jl:97 [inlined]
 [9] (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:73
ERROR: MethodError: Cannot `convert` an object of type Void to an object of type Float64
This may have arisen from a call to the constructor Float64(...),
since type constructors fall back to convert methods.
Stacktrace:
 [1] macro expansion at /Users/forets/.julia/v0.6/Requires/src/require.jl:52 [inlined]
 [2] my_fun(::Float64) at /Users/forets/Projects/Compositional/CompositionalHA.jl/test/Sets/bar.jl:4774:

why’s that? (without passing the C as argument, it works).

There’s Requires.jl.

1 Like

@tkoolen : Thank you.

I’ve edited the question with a minimal working example. I haven’t been able to get it work yet when the problem depends on some arguments that are defined at run-time :confused:

Can you do something like (untested):

my_fun(args...) = error("this function requires the JuMP module")

@require JuMP begin
  using GLPKMathProgInterface
  function my_fun(C::Float64)::Float64
    m = Model(...)
    # etc...
  end
end
1 Like

Last time I checked Requires.jl didn’t work well with macros, specifically with JuMP. @anon94023334 had an issue tracking this in LightGraphs.jl awhile back. I don’t know if that changed.

The issue, as I recall, had to do with the fact that macros are evaluated at compile-time, which means Requires.jl isn’t available.

i get UndefVarError: @variable not defined

(changing the first line to function my_fun(C::Float64) error("this function requires the JuMP module") end)