Good coding practices: How to cluster many variables in function argument

Hello there,

I’m still fairly new to julia, but I guess it is anyway a general programming question.
In my code there are several physical parameters, e.g. mass,speed,height,… as well as numerical parameters for different numerical methods used subsequently, e.g. n1,n2,n3,…, and m1,m2,m3,… (In reality I give them more useful names).

Now I wrote a function which does the main calculation

function fun_unclear(mass,speed,height,n1,n2,n3,m1,m2,m3)
end

However, as there are so many arguments (around 15 or so by now), I wonder which way is the best practice to “cluster” them in smaller groups e.g. having

function fun_clear(phys_params,n_params,m_params)
mass = phys_params[1]
...
end

in order to have more readable/easier debuggable code. One way I can think of is to introduce some arrays
phys_params = [mass,speed,height,...] before calling the function, but maybe there is another way which you recommend?

The parameters are all numbers, that is floats or integers.

Thank you for your input :slight_smile:

I would personally create a struct with named parameters and types:

struct SimParameters
    N::Int
    alpha:::Float64
    # ...etc
end

This is a pain when you need to add more as you need to restart the REPL if you change the struct, but it is clear what parameter is used in the code and makes the function signature much easier. The types are important for performance. You can make them generic as well if you want.
If there are default values you want, use Base.@kwdef:

Base.@kwdef struct SimParameters
    N::Int
    alpha:::Float64 = 1.0
    # ...etc
end
3 Likes

Another good option is Julia has NamedTuple packing/unpacking which is really good for quickly doing this:

function fun_clear(phys_params)
    (;mass, speed, height) = phys_params
    ...
end

phys_params = (;mass, speed, height) # if mass, speed, height are variables already defined somewhere
phys_params = (;mass=1, speed=2, height=3) # if you want to define them right here

fun_clear(phys_params)

The unpacking also works if phys_params is a custom struct like mentioned above.

7 Likes

This really depends on how the arguments logically relate to each other, where they come from, and how they are going to be used. Structs, mutable structs, tuples, named tuples, vectors, and hierarchical combinations of those can all be considered. Whichever way allows you to most easily reason about the code is probably the best solution.

1 Like

I vote for looking into named tuples as a good start. Then at some point later in the development you may find that some groups of parameters merit having their own composite type (struct), perhaps with some custom functionality or input checks etc.

4 Likes

Expanding on this. One important distinction is that struct, tuple, and named tuple are immutable, whereas mutable struct and vector are mutable. The biggest disadvantage of the immutable types are that you cannot change their content, which is really limiting if you need to do that. Their biggest advantage is that you cannot change their content, which means that you know that nobody will mess with them behind your back, e.g. deep down in some call chain. Conclusion: use immutable types unless you have a pressing need to update them in place.

Another distinction is that struct, mutable struct, and named tuple gives names to the elements, whereas tuple and vector don’t. Most of the time having names is helpful, but if the object is very short-lived or the elements are best described by a sequence number, names might just add cognitive overhead.

For the most part I would avoid vectors, unless those parameters are going to be used in vector operations.

Struct and named tuple are kind of similar, where the former involves more overhead but also provides more features. For just a single function call defining a struct is overkill but if you are going to pass it around between lots of functions, want to dispatch on it, or expose it to users, I’d prefer defining a struct.

2 Likes

Adding to the comments above, there does exist a package for mutable NamedTuples:

MutableNamedTuples.jl

Thank you for all your suggestions, as well as providing some background infos (pro/cons) and even small code examples. Very nice answers here!

I will then indeed start to use NamedTuples for now and keep the structs back in my head, in case I need something more “robust”.