Bug in the parse, inputParser family of functions?

I've stumbled upon what sure appears to me to be a bug in the parse and parseInput suite of functions. It seems to have to do with some combination of having character strings as arguments, having a valid function apply to check the validity of the arguments, and the order in which the arguements are provided. Here's perhaps the simpliest code to reproduce the issue:
tryOne = myParse('alpha','one', 'beta','two')
tryTwo = myParse('beta', 'two', 'alpha','one')
function results = myParse(varargin)
parseObj = inputParser;
addOptional(parseObj, 'alpha', 'none', @(x) ( isstring(x) || ischar(x) ) );
addOptional(parseObj, 'beta', 'none', @(x) ( isstring(x) || ischar(x) ) );
parse(parseObj,varargin{:})
results = parseObj.Results;
end
Note that the only difference between the first and second call to "myParse" is the order of the arguements. Running this little test code returns the wrong result in the first case:
tryOne =
struct with fields:
alpha: 'alpha'
beta: 'two'
Notice that 'alpha' is set to the string 'alpha' instead of being set to 'one', but 'beta' is set correctly. However, in the second call (tryTwo), with the order of the args just reversed, the correct result is returned.
TryTwo =
struct with fields:
alpha: 'one'
beta: 'two'
BTW, if I run the first try again, it gets the answer wrong again, so its not a matter of just the first call to parse returning the wrong answer.
I've tried all kinds of variants of the above, including numeric args, including or not including a validation check function in "addOptional", some of which seem to work correctly and some don't. But the example here seems to illustrate the issue most simply.
The parse/parseInput suite of functions are built-ins, so I can't dive into the code of those functions to figure out what is really going on here.

5 Comments

Peter
Peter on 6 Feb 2021
Edited: Peter on 6 Feb 2021
Forgot to say this behavior is seen in both is R2020b (MacOS) and R2020a (linux).
Also forgot to mention that if I leave the validation functions out (ie do not include the "@(x) ( istring(x) || ischar(x) )" of the addOptional statements, then the bug is not exposed. That is, both the "tryOne" and "tryTwo" runs return the expected results.
Seems odd... you should make a bug report.
@Peter can you tell us about the MATLAB Version you are using? you can use the ver command to know details about your MATLAB version.
This behavior was seen in both
i) R2020b Update 3 on a MacOS
ii) R2020a Update 1 on a linux (CentOS 7) platform.
Here's the output of ver from the MacOS machine:
----------------------------------------------------------------------------------------------------
MATLAB Version: 9.9.0.1538559 (R2020b) Update 3
MATLAB License Number: XXXXXXX
Operating System: macOS Version: 11.1 Build: 20C69
Java Version: Java 1.8.0_202-b08 with Oracle Corporation Java HotSpot(TM) 64-Bit Server VM mixed mode
-----------------------------------------------------------------------------------------------------
MATLAB Version 9.9 (R2020b)
Deep Learning Toolbox Version 14.1 (R2020b)
MATLAB Compiler Version 8.1 (R2020b)
Mapping Toolbox Version 5.0 (R2020b)
NCTOOLBOX Tools for read-only access to Common ... Version 1.1.1-13-g8582810++
Optimization Toolbox Version 9.0 (R2020b)
Statistics and Machine Learning Toolbox Version 12.0 (R2020b)
@Peter: I removed the license number from your comment. Best not to post that on the interweb.

Sign in to comment.

 Accepted Answer

Note that the only difference between the first and second call to "myParse" is the order of the arguements.
If the order is not supposed to matter, you should be using addParameter rather than addOptional,
tryOne = myParse('alpha','one', 'beta','two')
tryOne = struct with fields:
alpha: 'one' beta: 'two'
tryTwo = myParse('beta', 'two', 'alpha','one')
tryTwo = struct with fields:
alpha: 'one' beta: 'two'
function results = myParse(varargin)
parseObj = inputParser;
addParameter(parseObj, 'alpha', 'none', @(x) ( isstring(x) || ischar(x) ) );
addParameter(parseObj, 'beta', 'none', @(x) ( isstring(x) || ischar(x) ) );
parse(parseObj,varargin{:})
results = parseObj.Results;
end

4 Comments

To better understand what is going on when you use addOptional, observe the following modification of your example. Basically, the first pair of arguments are each being treated as positional arguments. They are not being treated as a Name/Value pair.
tryOne = myParse('one','two')
tryOne = struct with fields:
alpha: 'one' beta: 'two'
tryTwo = myParse('two', 'one')
tryTwo = struct with fields:
alpha: 'two' beta: 'one'
However, when you include a 3rd and 4th input, these are treated as a Name/Value pair. Moreover, even though alpha and beta have been declared as optional/positional, it appears that they can also be treated as name/value parameters and used to override their previous assignments in the argument list:
tryOne = myParse('one','two','alpha','duck duck')
tryOne = struct with fields:
alpha: 'duck duck' beta: 'two'
tryTwo = myParse('one', 'two','beta','goose')
tryTwo = struct with fields:
alpha: 'one' beta: 'goose'
I cannot tell if it is intentional that an argument can be both positional and a name/value keyword. That does seem odd.
function results = myParse(varargin)
parseObj = inputParser;
addOptional(parseObj, 'alpha', 'none', @(x) ( isstring(x) || ischar(x) ) );
addOptional(parseObj, 'beta', 'none', @(x) ( isstring(x) || ischar(x) ) );
parse(parseObj,varargin{:})
results = parseObj.Results;
end
Thank you Matt J. I clearly did not fully appreciate the nuance in the different behaviors of addOptional vs. addParameter. And as your example illustrates, the behavior is not always clear. The one thing that still seems odd to me is that if I did not include the validation function in addOptional, then the correct results came back regardless of the order of the Name-Value pair arguments when calling myParse. It was that dependency on whether or not this "issue" was exposed that partly convinced me there was a bug here. At minimum, there is odd behavior for sure. But thanks for the tips and advice.
function results = myParse(varargin)
parseObj = inputParser;
addOptional(parseObj, 'alpha', 'none');
addOptional(parseObj, 'beta', 'none');
parse(parseObj,varargin{:})
results = parseObj.Results;
end
Yes, that is very odd!
A little late to the party, but want to reiterate that you should use addParameter instead of addOptional because you intend to treat the inputs as name-value pairs instead of optional positional arguments.

Sign in to comment.

More Answers (0)

Categories

Find more on Argument Definitions in Help Center and File Exchange

Asked:

on 5 Feb 2021

Commented:

on 5 May 2023

Community Treasure Hunt

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

Start Hunting!