Readability of matlab code

34 views (last 30 days)
AM
AM on 5 Mar 2021
Edited: Bruno Luong on 8 Mar 2021
Hi,
I am coding using matlab and have many functions, some of which have more than a dozen input arguments. The majority of the input arguments of my different functions are variables that I read from an external file and that remain unchanged all throughout the calculations. Basically I read the external file once in my main script then give what has been read as input arguments to the other functions. For the readability of my code, I would like to not have that many input arguments and was wondering what is the best solution that would not negatively impact the performance of my code. I thought of the following:
  • use global variables, however I've read that it is best to avoid them
  • read the external file and save the different variables as .mat then load the arguments inside each function
  • define a cell variable in which I store all my arguments, e.g. cell={a;b;c} and give that as an input to my functions, then inside the function declare the variables as a=cell{1} etc
If anyone could help me choose the best solution or give me more ideas I would greatly appreciate it!
  2 Comments
Stephen23
Stephen23 on 5 Mar 2021
Edited: Stephen23 on 5 Mar 2021
  • global variables: ugh, no.
  • loading file data inside each function: slooooow.
  • a cell array: easy to get the elements mixed up, not easy to change.
Better solutions:
  • nested functions.
  • a scalar structure.
Personally I would not create new variables inside the functions, I would just refer directly to the data in the structure.
"the best solution that would not negatively impact the performance of my code"
Most likely the millisecond difference in timing is unlikely to be the biggest bottleneck in your code. And if accessing the data from a structure does take five minutes longer over ten hours run-time but saves you an hour of debugging, then you have still benefited. Don't optimize prematurely.
AM
AM on 5 Mar 2021
Scalar structures seem to be something that would work great in my case, I will look into them more, thank you very much!

Sign in to comment.

Accepted Answer

BC
BC on 5 Mar 2021
I recently had a similar question, and one of the options that was suggested was to use structures, e.g.
Function1_Parameter.Variable1 = 1
Function1_Parameter.Variable2 = 2
Function1_Parameter.Variable3 = 3
So then you only need to have the name of the structure (Function1_Parameter) in the input arguments of a function, and you can index whichever value you need, similar to your last suggestion.
  2 Comments
Steven Lord
Steven Lord on 5 Mar 2021
Another benefit of this is that you can pass the struct array into a function regardless of whether it uses all of those parameters, some of the parameters, or even (if you need a consistent interface) none of the parameters. You can add new parameters to the struct without affecting existing functions (they will just ignore the parameters they don't need.)
s = struct('a', 2, 'b', 4, 'c', 8);
f(1:10, s)
ans = 1×10
6 8 10 12 14 16 18 20 22 24
g(1:10, s)
ans = 1×10
0.1250 0.5000 1.1250 2.0000 3.1250 4.5000 6.1250 8.0000 10.1250 12.5000
s.d = 42;
f(1:10, s) % ignores the new parameter d
ans = 1×10
6 8 10 12 14 16 18 20 22 24
function y = f(x, s)
% use a and b from s but not c
y = s.a*x + s.b;
end
function y = g(x, s)
% use a and c from s but not b
y = (x.^s.a)./s.c;
end
AM
AM on 8 Mar 2021
Thank you!

Sign in to comment.

More Answers (3)

John D'Errico
John D'Errico on 5 Mar 2021
Edited: John D'Errico on 5 Mar 2021
AVOID GLOBAL VARIABLES. They will not improve your code. Intead they will make it difficult as hell to debug your code when there is a problem.
Next, don't just store all of your arguments in a cell array. That would simply force you to remember what parameter lies in ARGS{7}, etc. This is a recipe for buggy code, and certainly not readable code that can be maintained easily.
The various suggestions I see are all good ideas. Another idea is to use preferences, thus setpref and getpref. I use this sometimes when I want to set some general behavior for a function. Other things are property/value pairs, and structures. You can even combine all of these things in various ways. Let me give a few examples:
The examples I'll offer are my HPF toolbox, fmincon, and my SLM toolbox. How are each of these tools different?
For example, fmincon is a tool that has quite a few arguments, most of which are not used at least some of the time. The basic calling sequence for fmincon is something like this:
X = fmincon(FUN,X0,A,B,Aeq,Beq,LB,UB,NONLCON,OPTIONS)
Now much of the time, you may want to call fmincon, but only have a set of linear equality constraints on the objective. In that case, you call it like:
X = fmincon(FUN,X0,[],[],Aeq,Beq);
Or, if you have only bound constraints, then you need to write it as:
X = fmincon(FUN,X0,[],[],[],[],lb,ub);
And you need to know how many sets of empty brackets to put in there. This can be a confusing way to interact with the tool.
So an alternative way to have written fmincon might be to have only three arguments.
X = fmincon(FUN,X0,params);
Here params would be a structure, containing every possible piece of information fmincon would need. Thus if there are only upper and lower bounds on the problem, then we would have
params.lb = [1 2 3];
params.ub = [2 3 5];
If there were equality or inequality constraints on the problem, then params would have corresponding fields to supply them.
(Yes, the optimization toolbox tools in recent releases do now have better interfaces that allow for essentially this kind of interaction.)
I mentioned preferences before. In fact, if you have my hpf toolbox installed on your computer, then try accessing the prefs for 'hpf'. On my computer, I see this:
>> getpref('hpf')
ans =
struct with fields:
DefaultDecimalBase: 3
DefaultNumberOfDigits: [50 5]
In the preferences, that tool puts several pieces of information. The tool itself can then extract them as needed. Preferences are a convenient place to store such information. But as a tool, hpf only has a few such overarching parameters that you may wish to set. And that makes preferecnes a decent place to store and access them, especially since prefs are remembered across MATLAB sessions.
Next, look at my SLM toolbox. It has a function called slmset.
slmset
ans =
struct with fields:
C2: 'on'
ConcaveDown: 'off'
ConcaveUp: 'off'
ConstantRegion: []
Decreasing: 'off'
Degree: 3
EndConditions: 'estimate'
Envelope: 'off'
ErrorBar: []
Extrapolation: 'constant'
FminconAlgorithm: 'interior-point'
Increasing: 'off'
Integral: []
InteriorKnots: 'fixed'
Jerk: ''
Knots: 6
LeftMaxSlope: []
LeftMaxValue: []
LeftMinSlope: []
LeftMinValue: []
LeftSlope: []
LeftValue: []
LinearRegion: []
LsqlinAlgorithm: 'interior-point'
MaxFpp: []
MaxSlope: []
MaxValue: []
MinFpp: []
MinSlope: []
MinValue: []
NegativeInflection: []
Order: []
Plot: 'off'
PositiveInflection: []
Predictions: 1001
Regularization: 0.0001
Result: 'slm'
RightMaxSlope: []
RightMaxValue: []
RightMinSlope: []
RightMinValue: []
RightSlope: []
RightValue: []
Robust: 'off'
Scaling: 'on'
SegmentConstant: []
SegmentLinear: []
SimplePeak: []
SimpleValley: []
SumResiduals: []
Verbosity: 0
Weights: []
XY: []
XYP: []
XYPP: []
XYPPP: []
YScale: []
YShift: []
slmset is a function that returns a structer with the default value for every possible property. You can also call slmset with property value pairs to create a similar structure with your choice of properties. Calling the slmengine code with no other arguments than some data, and you get all the defaults from slmset applied, thus:
spl = slmengine(x,y)
x = 1:5;
y = rand(1,5);
slm = slmengine(x,y)
slm =
struct with fields:
form: 'slm'
degree: 3
knots: [6×1 double]
coef: [6×2 double]
stats: [1×1 struct]
prescription: [1×1 struct]
x: [5×1 double]
y: [5×1 double]
Extrapolation: 'constant'
As you see in the call to slmset, the default for 'knots' was 6. If you want to use some other value than the default, you override it by providing the specific property, as:
spl = slmengine(x,y,'knots',12)
So here 12 knots would be used, but all other properties reside at their default values.
Had I wanted to be even more user friendly, suppose you alwys find yourself changing my defaults for some property? Then I might have used preferences to allow that to work. (Note that this is NOT something that slmset checks, but I could have written it that way, to be even more user friendly.)
setpref('slm','knots',12)
I could now access that preference.
getpref('slm','knots')
ans =
12
There are surely many other schemes that can be used. And some schemes will be better than others in various applications.
  1 Comment
AM
AM on 8 Mar 2021
Thank you very much for taking the time to answer my question in such detail !

Sign in to comment.


Cris LaPierre
Cris LaPierre on 5 Mar 2021
Edited: Cris LaPierre on 5 Mar 2021
I would not recommend global variables.
Perhaps look into nested functions. All variables defined in the parent function are accessable inside a nested function without having to pass them in.
You can see an example in this answer.

Bruno Luong
Bruno Luong on 8 Mar 2021
Edited: Bruno Luong on 8 Mar 2021
Why not use object oriented programing? Put your external parameters in properties. Put your functions as methods.
Implement an Initialization function that load the external file and populate the properties.
All the methods has then access to the properties.

Products


Release

R2020b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!