Modules VS Passing arguments to functions

Hello,

I developed a numerical solver for a certain PDE.
In these situations it’s quite normal to test the algorithm with several different examples and have many parameters, arrays and anonymous functions that I must work with, in this case are about 20 quantities.

My code is divided by functions that I made. Because of that, in most of the cases I have to pass too many arguments to the functions. Like this:

A,B,C,D,E,F,G,H,I,J,L = initParameters(example)
...
eqSolved = solveEq(A,B,C,D,E,F,G,H,I,J,L,M,N,O)

And the function solveEq is defined like this:

function solveEq(A::Array{Complex{Float64},2},B::Array{Float64,2},C::Float64,D::Array{Float64,2},E,F,G::Float64,H::Float64,I::Int64,J::Array{Float64,2},L::Float64,M::Array{Float64,2},N::Array{Float64,1},O::Array{Float64,3})
    ...
    return eqSolved
end

Since the huge amount of parameters that I have to pass as arguments to the function and due the fact that most of them are fixed quantities, like the length of the domain, time, etc, etc. I was thinking that a better way to do this was to store the parameters in a module and load it when I want.

module init_example1
export A,B,C,D,E,F,G,H,J,L
A = 1
B = 2
...
end

Then I load the module and export just the variables that I want in that specific function, like this:

using .init_example1: A,B,C,D
...
O = fill(sqrt(A)+randn(1),(B,C,D))
...
eqSolved = solveEq(O)

Finally the function solveEq in this case has this structure:

function solveEq(O::::Array{Float64,3})
    using .init_example1: E,F,G,H,I,J,L,M,N
    ...
    return eqSolved
end

This is a good/performative approach?
If I store the variables in a module they become global, therefore my code will be slower than it was? This is a real issue for me, because I’m running many simulations the code takes about 1h30 hours to run.

Any sugestions or I’m doing a good approach by using modules?

Thank you!

1 Like

In general you don’t want to resort to global variables, especially if they are not const. There are a number of alternatives here. You might want to consider using splatting. For example, if your function has standard inputs, you could define another method for solveEq so that you can do

solveEq(args) = solveEq(args...)
solveEq(initParameters(example))

If you want a little more structure to it, you could also do this with NamedTuple.

solveEq(args::NamedTuple) = solveEq(args.A, args.B, args.C, args.D, args.E, args.F, args.J, args.L)
solveEq(;kwargs...) = solveEq((;kwargs...))

Note that it’s possible to have mandatory keyword arguments (just don’t specify default arguments).

Also, you should consider adding more methods to your functions which accept the initialization arguments.

solveEq(ex::typeof(example)) = solveEq(initParameters(ex)...)

So there are a lot of ways of dealing with this sort of thing, without defining modules.

2 Likes

Parameters.jl is great for this sort of thing. In particular, note the @unpack_ macro, which lets you dump all of the parameters into a function’s namespace without needing to pass them in as arguments.

5 Likes

Indeed, wrapping in a struct is definitely the way to go.

1 Like

Hmm, ok I got it!
I didn’t remember that I could pass the tuple of the initParameters output as an argument to solveEq.
Thanks for the help, I will try that approach!!

Oh thank you! I didn’t knew Parameters.jl existed!!
I will read about it and try it in my code!