I have some MATLAB functions with a large number of optional parameters, that I want to specify as name/value arguments to keep the interfaces clear and avoid the confusion that comes with a large number of positional arguments.
There is a hierarchy of functions, where higher-level functions have all the same arguments as lower-level functions, plus some additional ones (in fact the functions are constructors in a class hierarchy - I don't think that is relevant, but including it here in case it is).
An example would be something like the below. As you can see, there is a lot of repeated code (typically there are dozens of name/value arguments, not just the few shown here, and dozens of different functions). The main problems with this approach are:
arguments
block, together with their default initializations. I would prefer to only list the arguments in the lower level functions, and have the defaults be set by lower level functions in the case that they are not set by higher level functions.function highLevelFunc(opts)
arguments
opts.highLevelArg_1 = [];
opts.highLevelArg_2 = [];
opts.highLevelArg_3 = [];
opts.midLevelArg_1 = [];
opts.midLevelArg_2 = [];
opts.midLevelArg_3 = [];
opts.lowLevelArg_1 = [];
opts.lowLevelArg_2 = [];
opts.lowLevelArg_3 = [];
end
midLevelResult = midLevelFunc(...
midLevelArg_1 = opts.midLevelArg_1, ...
midLevelArg_2 = opts.midLevelArg_2, ...
midLevelArg_3 = opts.midLevelArg_3, ...
lowLevelArg_1 = opts.lowLevelArg_1, ...
lowLevelArg_2 = opts.lowLevelArg_2, ...
lowLevelArg_3 = opts.lowLevelArg_3 ...
);
% high level code goes here
end
function midLevelFunc(opts)
arguments
opts.midLevelArg_1 = [];
opts.midLevelArg_2 = [];
opts.midLevelArg_3 = [];
opts.lowLevelArg_1 = [];
opts.lowLevelArg_2 = [];
opts.lowLevelArg_3 = [];
end
lowLevelResult = lowLevelFunc(...
lowLevelArg_1 = opts.lowLevelArg_1, ...
lowLevelArg_2 = opts.lowLevelArg_2, ...
lowLevelArg_3 = opts.lowLevelArg_3 ...
);
% mid-level code goes here
end
function lowLevelFunc(opts)
arguments
opts.lowLevelArg_1 = [];
opts.lowLevelArg_2 = [];
opts.lowLevelArg_3 = [];
end
% low-level code goes here
end
I hoped that I could use varargin
to avoid repeating argument names, but it seems that the language does not allow this if I want to use it in combination with name/value arguments. The code below gives me an error "Positional arguments must be defined before name-value arguments".
function midLevelFunc(opts, varargin)
arguments
opts.midLevelArg_1 = [];
opts.midLevelArg_2 = [];
opts.midLevelArg_3 = [];
end
arguments (Repeating)
varargin
end
lowLevelResult = lowLevelFunc(varargin{:});
% mid-level code goes here
end
I could put the varargin
before the name/value block, but I think this then forces the calling function to put lower-level arguments before higher-level arguments when calling the higher-level function, which feels unintuitive:
function midLevelFunc(varargin, opts)
arguments (Repeating)
varargin
end
arguments
opts.midLevelArg_1 = [];
opts.midLevelArg_2 = [];
opts.midLevelArg_3 = [];
end
lowLevelResult = lowLevelFunc(varargin{:});
% mid-level code goes here
end
Are there better solutions here?
Between nargin
and the arguments
syntax came the inputParser
which can achieve this through a combination of
KeepUnmatched
property of the parser to true
namedargs2cell
to pass through the unmatched arguments to the next levelWith your example, it would look something like this, where you only have to maintain the lower level name-value pairs in their respective functions
function highLevelFunc( varargin )
p = inputParser();
p.addParameter( 'highLevelArg', 'highDefault' );
p.KeepUnmatched = true;
p.parse( varargin{:} );
args = namedargs2cell( p.Unmatched );
midLevelFunc( args{:} );
% high-level code goes here
highLevelArg = p.Results.highLevelArg;
fprintf( 'High level arg: %s\n', highLevelArg );
end
function midLevelFunc( varargin )
p = inputParser();
p.addParameter( 'midLevelArg', 'midDefault' );
p.KeepUnmatched = true;
p.parse( varargin{:} );
args = namedargs2cell( p.Unmatched );
lowLevelFunc( args{:} );
% mid-level code goes here
midLevelArg = p.Results.midLevelArg;
fprintf( 'Mid level arg: %s\n', midLevelArg );
end
function lowLevelFunc( varargin )
p = inputParser();
p.addParameter( 'lowLevelArg', 'lowDefault' );
p.parse( varargin{:} );
% low-level code goes here
lowLevelArg = p.Results.lowLevelArg;
fprintf( 'Low level arg: %s\n', lowLevelArg );
end
Test run where we only overwrite the lowest input:
>> highLevelFunc( 'lowLevelArg', 'test' )
Low level arg: test
Mid level arg: midDefault
High level arg: highDefault
For your application to super/sub classes, there is some functionality to inherit properties of the superclass as additional name-value pairs using opts.?SuperClass
in the arguments
block, but if you just want name-value pairs without them being properties I'm not sure that functionality exists. Ref https://mathworks.com/help/matlab/matlab_oop/class-constructor-methods.html#mw_749777e1-ea34-4e8e-b4d3-317d6d61131c