Is there a simple way to convert all fields of a struct to local variables?
90 views (last 30 days)
Show older comments
I have a matlab program with a lot of variables (boolean, arrays, doubles, logicals, strings), to the point where it becomes unwieldy to alter the workflow. I often don't know which I'm going to need when I add new functionality.
So if I create a function with a series of recursive function calls and X layers deep I discover I need a variable I didn't feed into it, I need to add those variables in the right order as arguments to every function in the recursion. This seems both risky and tedious. It also makes for messy code as functions get passed 20+ arguments.
So I created a struct "programConstants" at the beginning of the program flow that aggregates all of the variables inside of it. So I can access them like:
programConstants.someThing;
Is there a simple way I can just dump all of the programConstants fields into the scope of a function when I pass it?
I would like to avoid accessing the variables from the struct. For example, if I want "someThing", I'd like to just be able to write:
someThing;
instead of:
programConstants.someThing;
7 Comments
Stephen23
on 15 Aug 2018
Edited: Stephen23
on 15 Aug 2018
"I'm trying to avoid this approach as there is a lot of code (~100 files) that would need to be updated to accommodate the ~50 possible parameters."
This is a one time cost, which will be worth the investment. The advanced Find Files tool, or a good text editor could probably help you.
Do this properly once, rather than writing hack code to magically make variables appear all over the place (which will ultimately just cost you more time debugging and consume memory and slow your code).
Using one structure will save on memory usage: simply pass that structure to all 100 functions, and MATLAB will not actually copy the data in memory (as long as the data does not change): all 100 functions can happily use one instance of that structure in memory. In contrast when you magically create those variables in each workspace, you just pointlessly duplicate all of the variables 100 times. So not only will it slow your code down and make it buggier, it will pointlessly waste memory.
"Not all of the functions get passed an unwieldy amount of parameters, but I figured consistency would be best."
Probably. Do it once, do it right.
dpb
on 15 Aug 2018
Amplifying on the above, with the structure you don't have to do every function at once; all you must do is begin with the one(s) you're working on actively at the moment and change their interface and add the structure name prefix to the variable(s) being used internal to the function (you did keep those consistent, right?). Everything else can remain as is with the variables existing as separate; you simply have to update the structure as well if it contains results as well as inputs.
This is a less than ideal implementation but will let you make the transition over time rather than requiring a complete refactoring all at once.
But I'd reiterate the others--it'll pay large dividends in the long run if this application has any future life expectancy as sounds as does...
Accepted Answer
Steven Lord
on 15 Aug 2018
I have a matlab program with a lot of variables (boolean, arrays, doubles, logicals, strings), to the point where it becomes unwieldy to alter the workflow. I often don't know which I'm going to need when I add new functionality.
That does sound problematic. The risk of breaking such tightly coupled code is high if you're not very careful.
So if I create a function with a series of recursive function calls and X layers deep I discover I need a variable I didn't feed into it, I need to add those variables in the right order as arguments to every function in the recursion. This seems both risky and tedious. It also makes for messy code as functions get passed 20+ arguments.
I agree with your assessment about the risk, mess, and tediousness.
This isn't directly related to your main question, but one way to reduce some of the risk associated with modifying or refactoring your functions would be to write tests for your functions that can reduce the chance that you'll accidentally change the behavior that some other function depends upon when you update your function. This could also help you if you want to refactor your code to make it easier to understand and extend in the future: extract a small segment of functionality into its own function, unit test it thoroughly, and add an integration or system test for the function(s) that call it.
So I created a struct "programConstants" at the beginning of the program flow that aggregates all of the variables inside of it.
IMO that's a step in the right direction, but you have everything crammed into one struct array. Are there logical groupings of the information that your functions need where you could create multiple struct arrays or perhaps even separate classes? So instead of having programConstants you could have carParameters, personParameters, etc.?
Is there a simple way I can just dump all of the programConstants fields into the scope of a function when I pass it?
There are ways to do what you ask, but I recommend against it. If one of the fields in your programConstants struct matches the name of a MATLAB function (commonly-matched functions include alpha, sum, min, max, etc.) then depending on what you do in your function MATLAB may call the function rather than referring to the variable that was "poofed" like a magician's rabbit into the workspace at run-time.
If you're only going to use a particular piece of data from the struct once in your code, consider just accessing it from the struct. If you know you're going to use a particular piece multiple times, then it may be time to define a local variable. But I would limit this to just what your function is going to use. This would be more work than simply dumping everything from the struct into the workspace, but it would also be less likely to cause the "poofing" confusion I described in the previous paragraph.
4 Comments
Steven Lord
on 15 Aug 2018
We have started writing tests a few months back, but there is a lot of legacy code without tests. And unfortunately - writing tests then refactoring the arguments means you have to refactor the tests too.
It can be painful, I agree. But it sounds like you're approaching this from the top down, starting your refactoring with the whole application. While that seems like the obvious starting point (begin at the beginning) that's also the most daunting approach IMO.
Instead consider isolating the commonly used lowest level pieces first. Decide what single responsibility each of those small pieces should have (splitting those small pieces that are trying to do too much), lock down their interface, unit test them, and then hopefully you never have to touch those functions (or their tests) again.
You will need to change the callers of those functions to react to any interface change, but you would have had to do that anyway. And this way you only need to keep a small chunk of the overall system in your mental cache at once.
Stephen23
on 16 Aug 2018
Edited: Stephen23
on 16 Aug 2018
@Doug Rank: for a project of that size and complexity you need to be using best practice, such as using and following the advice of the static code checking tools to ensure that every function does not have any warnings or obvious bottlenecks. This then requires you to not magically make variables appear in any workspaces, because static code checking tools don't work with variables that magically appear at run time!
Best practice would also include using a good versioning system, e.g. GIT. This will make updating and keeping track of the versions much simpler.
More Answers (2)
OCDER
on 15 Aug 2018
Look into inputParser and the StructExpand option. https://www.mathworks.com/help/matlab/ref/inputparser.html
% S = struct('Var1', 1, 'Var2', 2, 'Var3', 3, 'Var4', 4);
% testFcn(S)
function A = testFcn(varargin)
P = inputParser;
P.StructExpand = true;
P.KeepUnmatched = true;
addParameter(P, 'Var1', 0, @isnumeric);
addParameter(P, 'Var2', 0, @isnumeric);
addParameter(P, 'Var3', 0, @isnumeric);
%addParameter(P, 'Var4', 0, @isnumeric); %This function happens to not need Var4, for example.
parse(P, varargin{:});
P = P.Results;
P %show the output
Or you could just pass the structure itself.
function A = testFcn(S)
if ~isfield(S, 'Var1'); S.Var1 = 1; end
..
0 Comments
Pouya
on 13 Mar 2024
Edited: Pouya
on 13 Mar 2024
Yoiu can use fieldnames function in conjuctions with assignin
The assignin function is handy for this task, as it allows you to assign values to variables in either the base workspace or the current function/workspace.
% Get a list of all field names in the structure
fieldNames = fieldnames(programConstants);
% Loop over each field name and assign its corresponding value in 'data'
% to a variable with the same name in the base workspace
for i = 1:length(fieldNames)
fieldName = fieldNames{i};
fieldValue = data.(fieldName);
% Assign the field value to a variable with the same name in the base workspace
assignin('base', fieldName, fieldValue);
end
Alternatively you can wrap the same code into a handy function and use 'caller' instead of 'base'. :
function structToVariablesCaller(inputStruct)
fieldNames = fieldnames(inputStruct);
for i = 1:length(fieldNames)
fieldName = fieldNames{i};
fieldValue = inputStruct.(fieldName);
assignin('caller', fieldName, fieldValue);
end
end
2 Comments
Rik
on 13 Mar 2024
As explained elsewhere in this thread, this method of creating variables out of thin air is not a good idea. It is a lot harder to track variables this way, making debugging needlessly difficult.
It sounds like you think 'base' is the current workspace. If so: that is incorrect. Each function has its own workspace, so if you use the first suggestion inside a function, it will not work.
And a final note: you should use numel or size. There is hardly ever a reason to use length, and this is no exception.
See Also
Categories
Find more on Variables in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!