Why and when to pass variables to sub-functions?

I am working on spitting my functions up into many smaller sub-functions. The issue I came across is that I need to pass more and more variables to successive sub-functions. How is this typically handled?

I see three possibilities:

  1. Pass all the needed variables to every sub-function.
  2. Don’t pass any variables to sub-functions and rely on inheritance from outer scope.
  3. Re-compute quick one-liners like array sizes inside every sub-function and then only pass the arrays themselves.

Option 1 seems verbose, Option 2 seems error prone, and Option 3 seem redundant. A sample function is below.

function func(v::Vector,i2::Int)
    i1=length(v)
    a1::Array=subfunc(v,i2) # i2=size(a1,1)
    i3=size(a1,2)
    (a2,a3)=mainfunc(v,a1,i1,i2,i3)
    auxfunc1(v,a1,a2,a3,i1,i2,i3)
    auxfunc2(v,a1,a2,a3,i1,i2,i3)
end
1 Like

Option (2) makes testing any of the subfunctions in isolation difficult or impossible, so I wouldn’t recommend it. If you’re talking about things like size(a1, 1) as the values you re-compute, then I would definitely recompute them and only pass the array itself. It’s unusual and generally unnecessary to pass an array and the size of that array to a function in Julia. There’s no particular performance benefit to doing so (calling size() should be very nearly free), so all it does is make your function more difficult to correctly use with no perceptible gain.

For quantities which actually take some effort to compute, of course, the answer may be different. But that doesn’t seem to be what you’re talking about here.

7 Likes

Computing array sizes and the like is super efficient. You should not worry about things like that.

I’m not sure exactly what you mean by option 2 but it feels like you are talking about global variables, which should be avoided if you care about performance (and reducing silly errors).

If you have tons of variables/parameters/inputs, think about collecting them into some sort of structure. A simple named tuple might be enough, or create your own type.

1 Like

It might make sense to combine some of your arguments together in one or more composite types. Things that always go together ought to be held together by some structure.

3 Likes

Do you even bother saving array sizes to variables then if the size function is almost free? I thought this was good practice, so I could reuse them for all future for loops. But maybe all your for loops look like for i in 1:size(a,1) rather than for i in 1:il since small functions seem to kill reusability here anyway.

1 Like

When I save something like this to a variable, it’s usually for character savings, not perf

3 Likes

It’s fine to save things like size to local variables if it helps you organize or structure your code. If the calls to size() are making your for loop unreadably long, then by all means use local variables to clarify what’s going on. There’s just no need to pass those values along with the arrays themselves into other functions.

I would suggest trying to optimize for the ease of reading your code in the future–a local variable could make the intent of the code more obvious, but if its definition was long ago then a future reader might have to backtrack a lot to figure out what’s going on. The good news is that you should get good performance in Julia regardless of what you choose.

5 Likes