When using function argument validation that depends on multiple
arguments in a (Repeating)
arguments block, the current argument is passed
to the validation function normally while other arguments are passed as
partially-populated
cell arrays. This contasts with how things work in non-(Repeating)
arguments blocks. Is this the expected behavior or a bug?
Consider the function dummy1
below, which uses the custom argument validation
function
mustBeEqualSize
to ensure that the arguments x
and y
have the same size.
Since the validation of y
depends on the value of x
, I'm calling this
"cross-argument" validation*.
*If there's a better term for this, please comment or edit.
function dummy1(x, y)
arguments
x (1,:)
y (1,:) {mustBeEqualSize(x,y)}
end
% Do something with x and y.
end
function mustBeEqualSize(a, b)
% Validates that function arguments have the same size.
if ~isequal(size(a), size(b))
eid = 'Size:notEqual';
msg = "Arguments must have the same size.";
throwAsCaller(MException(eid, msg))
end
end
As written, this form of argument validation works as expected:
dummy1(1:3, 4:6) % arguments have same size; validation passes (okay)
dummy1(1:3, 4:7) % arguments have different size; validation fails (okay)
In both cases, when mustBeEqualSize
is called during argument
validation for y
, both a
and b
are received as 1xN double arrays,
which matches my expectations:
% Inside call to mustBeEqualSize(x,y), when x=1:3, y=4:6 in dummy1
a =
1 2 3
b =
4 5 6
The problem arises when dummy1
is modified to accept repeating
arguments by adding (Repeating)
to the arguments block:
function dummy2(x, y)
arguments (Repeating)
x (1,:)
y (1,:) {mustBeEqualSize(x,y)}
end
% Do something with each pair of x-y arguments.
% In this body, both x and y will be 1xN cell arrays, where N is the
% number of argument groups passed.
end
Now when we call dummy2(1:3, 4:6)
, argument validation fails. Using the
debugger, I found that when mustBeEqualSize
is called during the
validation of y
, a
is received as a 1x1 cell array while b
remains a 1x3 double array:
% Inside call to mustBeEqualSize(x,y), when x=1:3, y=4:6 in dummy2
a =
1×1 cell array
{[1 2 3]}
b =
4 5 6
The issue is even more obvious when more repeating argument are used:
dummy2(1:3, 4:6, 1:3, 4:6, 1:3, 4:6) % 3 argument groups
results in
a =
1×3 cell array
{[1 2 3]} {0×0 double} {0×0 double}
b =
4 5 6
It seems that during the validation of y
in dummy2
, y
takes on the value of the
current argument being validated while x
is a (parially populated)
cell array buffer that MATLAB allocated to hold all of the x
-arguments
that were passed.
This of course breaks the cross-argument validation, since only the argument currently being validated actually presents itself as a single argument, while other arguments present themsevles as cell array buffers.
Is the mismatch between how cross-argument validation works for (Repeating)
versus non-(Repeating)
arguments a bug, or is cross-argument validation with (Repeating)
arguments not supported in MATLAB? If this difference in behavior is expected, is there any way to make cross-argument validation work with (Repeating)
arguments?
The MATLAB docs say the following about argument validation with Repeating arguments
In the function, each repeating argument becomes a cell array with the number of elements equal to the number of repeats passed in the function call. The validation is applied to each element of the cell array.
which doesn't seem to shed any light on how cross-argument validation should work with repeating arguments.
Tested using MATLAB R2021a (9.10.0.1602886).
Note: This was an edge case of the design we didn't consider. I've made the relevant devs teams aware and they'll consider fixing it in a future release.
In the short term, you could try grabbing the last non 0x0 double
element of the cell array:
function mustBeEqualSize(x,y)
if iscell(x)
% Get last non-empty element of `x`
notEmptyDouble = @(e)isa(e,'double') && ~isequal(size(e), [0, 0]);
idx = find(cellfun(notEmptyDouble, x), 1, 'last');
x = x{idx};
end
% check equality
if ~isequal(size(x), size(y))
error("size mismatch")
end
end
This will work on all non-empty arrays, but unfortunately given the empty type used, will not work for dummy2([],[])
.
Also, I want to note, that whatever validator ends up working for this use case, consider if it will break the function if/when this issue is fixed. I.e. if this function needs to handle both double arrays and cell arrays, could be problematic in the future. However if this function only needs double arrays, it can be constrained using
arguments(Repeating)
a (1,:) double
b (1,:) double {mustBeEqualSize(x,y)}
end
This combined with the iscell
check in the validator, should future-proof this function.