Я мог бы часами тратить на это, но у меня все еще нет хорошего гештальтового представления об общей обработке подписи Matlab. Но вот пара советов.
Во-первых, возьмите laissez faire подход к проверке типов ввода. Доверяй звонящему. Если вы действительно хотите сильное тестирование типов, вам нужен статический язык, такой как Java. Старайтесь обеспечивать безопасность типов везде, где есть в Matlab, и у вас в конечном итоге будет значительная часть вашего LOC и время выполнения, посвященное тестам времени выполнения и принуждению в пользовательском пространстве, что обуславливает большую мощность и скорость разработки Matlab. , Я научился этому нелегко.
Для сигнатур API (функций, предназначенных для вызова из других функций, а не из командной строки), рассмотрите возможность использования одного аргумента Args вместо varargin. Затем его можно передавать между несколькими аргументами без необходимости преобразовывать его в разделенный запятыми список для подписей varargin. Структуры, как говорит Джонас, очень удобны. Есть также хороший изоморфизм между структурами и n-by-2 {name, value; ...} ячейками, и вы можете настроить пару функций для преобразования между ними внутри ваших функций в зависимости от того, что он хочет использовать внутри.
function example(args)
%EXAMPLE
%
% Where args is a struct or {name,val;...} cell array
Независимо от того, используете ли вы inputParser или прокручиваете свой собственный синтаксический анализатор name / val, как в этих других прекрасных примерах, упакуйте его в отдельную стандартную функцию, которую вы будете вызывать из верхней части ваших функций, имеющих сигнатуры name / val. Сделайте так, чтобы он принимал список значений по умолчанию в структуре данных, которую удобно записывать, и ваши вызовы синтаксического анализа будут выглядеть как объявления сигнатур функций, которые помогают удобочитаемости и позволяют избежать копирования и вставки стандартного кода.
Вот как могут выглядеть парсинговые вызовы.
function out = my_example_function(varargin)
%MY_EXAMPLE_FUNCTION Example function
% No type handling
args = parsemyargs(varargin, {
'Stations' {'ORD','SFO','LGA'}
'Reading' 'Min Temp'
'FromDate' '1/1/2000'
'ToDate' today
'Units' 'deg. C'
});
fprintf('\nArgs:\n');
disp(args);
% With type handling
typed_args = parsemyargs(varargin, {
'Stations' {'ORD','SFO','LGA'} 'cellstr'
'Reading' 'Min Temp' []
'FromDate' '1/1/2000' 'datenum'
'ToDate' today 'datenum'
'Units' 'deg. C' []
});
fprintf('\nWith type handling:\n');
disp(typed_args);
% And now in your function body, you just reference stuff like
% args.Stations
% args.FromDate
А вот функция для реализации анализа имени / val таким образом. Вы можете выдвинуть это и заменить его на inputParser, ваши собственные соглашения о типах и т. Д. Я думаю, что соглашение о ячейках n-на-2 создает приятный для чтения исходный код; рассмотреть вопрос о сохранении этого. Структуры обычно удобнее иметь в принимающем коде, но ячейки n-на-2 удобнее создавать с использованием выражений и литералов. (Структуры требуют продолжения ", ..." в каждой строке и защиты значений ячеек от расширения до нескалярных структур.)
function out = parsemyargs(args, defaults)
%PARSEMYARGS Arg parser helper
%
% out = parsemyargs(Args, Defaults)
%
% Parses name/value argument pairs.
%
% Args is what you pass your varargin in to. It may be
%
% ArgTypes is a list of argument names, default values, and optionally
% argument types for the inputs. It is an n-by-1, n-by-2 or n-by-3 cell in one
% of these forms forms:
% { Name; ... }
% { Name, DefaultValue; ... }
% { Name, DefaultValue, Type; ... }
% You may also pass a struct, which is converted to the first form, or a
% cell row vector containing name/value pairs as
% { Name,DefaultValue, Name,DefaultValue,... }
% Row vectors are only supported because it's unambiguous when the 2-d form
% has at most 3 columns. If there were more columns possible, I think you'd
% have to require the 2-d form because 4-element long vectors would be
% ambiguous as to whether they were on record, or two records with two
% columns omitted.
%
% Returns struct.
%
% This is slow - don't use name/value signatures functions that will called
% in tight loops.
args = structify(args);
defaults = parse_defaults(defaults);
% You could normalize case if you want to. I recommend you don't; it's a runtime cost
% and just one more potential source of inconsistency.
%[args,defaults] = normalize_case_somehow(args, defaults);
out = merge_args(args, defaults);
%%
function out = parse_defaults(x)
%PARSE_DEFAULTS Parse the default arg spec structure
%
% Returns n-by-3 cellrec in form {Name,DefaultValue,Type;...}.
if isstruct(x)
if ~isscalar(x)
error('struct defaults must be scalar');
end
x = [fieldnames(s) struct2cell(s)];
end
if ~iscell(x)
error('invalid defaults');
end
% Allow {name,val, name,val,...} row vectors
% Does not work for the general case of >3 columns in the 2-d form!
if size(x,1) == 1 && size(x,2) > 3
x = reshape(x, [numel(x)/2 2]);
end
% Fill in omitted columns
if size(x,2) < 2
x(:,2) = {[]}; % Make everything default to value []
end
if size(x,2) < 3
x(:,3) = {[]}; % No default type conversion
end
out = x;
%%
function out = structify(x)
%STRUCTIFY Convert a struct or name/value list or record list to struct
if isempty(x)
out = struct;
elseif iscell(x)
% Cells can be {name,val;...} or {name,val,...}
if (size(x,1) == 1) && size(x,2) > 2
% Reshape {name,val, name,val, ... } list to {name,val; ... }
x = reshape(x, [2 numel(x)/2]);
end
if size(x,2) ~= 2
error('Invalid args: cells must be n-by-2 {name,val;...} or vector {name,val,...} list');
end
% Convert {name,val, name,val, ...} list to struct
if ~iscellstr(x(:,1))
error('Invalid names in name/val argument list');
end
% Little trick for building structs from name/vals
% This protects cellstr arguments from expanding into nonscalar structs
x(:,2) = num2cell(x(:,2));
x = x';
x = x(:);
out = struct(x{:});
elseif isstruct(x)
if ~isscalar(x)
error('struct args must be scalar');
end
out = x;
end
%%
function out = merge_args(args, defaults)
out = structify(defaults(:,[1 2]));
% Apply user arguments
% You could normalize case if you wanted, but I avoid it because it's a
% runtime cost and one more chance for inconsistency.
names = fieldnames(args);
for i = 1:numel(names)
out.(names{i}) = args.(names{i});
end
% Check and convert types
for i = 1:size(defaults,1)
[name,defaultVal,type] = defaults{i,:};
if ~isempty(type)
out.(name) = needa(type, out.(name), type);
end
end
%%
function out = needa(type, value, name)
%NEEDA Check that a value is of a given type, and convert if needed
%
% out = needa(type, value)
% HACK to support common 'pseudotypes' that aren't real Matlab types
switch type
case 'cellstr'
isThatType = iscellstr(value);
case 'datenum'
isThatType = isnumeric(value);
otherwise
isThatType = isa(value, type);
end
if isThatType
out = value;
else
% Here you can auto-convert if you're feeling brave. Assumes that the
% conversion constructor form of all type names works.
% Unfortunately this ends up with bad results if you try converting
% between string and number (you get Unicode encoding/decoding). Use
% at your discretion.
% If you don't want to try autoconverting, just throw an error instead,
% with:
% error('Argument %s must be a %s; got a %s', name, type, class(value));
try
out = feval(type, value);
catch err
error('Failed converting argument %s from %s to %s: %s',...
name, class(value), type, err.message);
end
end
К сожалению, строки и даты не являются первоклассными типами в Matlab.