Better MATLAB functions with the inputParser class

Anyone whose written their own functions in MATLAB has probably been frustrated by the language's lack of flexibility when it comes to function arguments (especially when compared to other languages used for similar tasks, like R and Python).

Features like optional arguments with default values rely on the function author coding ad-hoc solution in the function body itself. This typically involves brittle solutions like sniffing the number of inputs with nargin or varargin and forcing the user to supply empty arrays in place of the argument they want to be "missing", like so:

some_random_function(x, [], [], [], [], 10)

As a user, good luck remembering that you want 10 to be the 6th argument, not the 5th or 7th, without first getting it wrong 2 times. As a developer, good luck extending your function to take one more argument, or one fewer, without having to rewrite your entire argument parsing logic. And named arguments, or a flexible argument order? Nope, sorry, not here.

Thankfully, the inputParser class provides a coherent way to parse function arguments that allows your function to:

  • Have optional arguments, with default values if omitted
  • Have a flexible argument order
  • Have named arguments in the functon call (well, almost =) )
  • Validate inputs (no more figuring out the input is bad by trying to use it!)

inputParser is also supported in Octave >= 4.0, so your code will be portable across environments (with some small caveats).

Writing a function using inputParser

To see how to use the inputParser class, we're going to write a variance function that takes 3 inputs

  1. x (Unnamed, Required, Positional): A numeric vector to find the variance of. Must be first argument supplied.
  2. RemoveMissing (Named, Optional, a-positional): A logical value controlling whether the calculated value will be an unbiased estimate of the population variance, or the sample variance. Can be supplied in any position after first argument.
  3. VarianceType (Named, Optional, a-positional): A character string controlling whether the calculated value will be an unbiased estimate of the population variance, or the sample variance. Can be supplied in any position after first argument.

Step 1: Declaring the function

Any unnamed, positional arguments should be declared first. In our case, this means declaring x as the first argument.

If you want your function to accept optional, named arguments, you end the arguments list with the varargin keyword, which is a catch-all for any additional inputs (similar to the ... construct in R).

function [ var ] = variance(x, varargin)

Step 2: Create the inputParser instance

The next thing you need is an inputParser object, which is created with the inputParser function. If you are unfamiliar with objects in MATLAB or another programming language, you can think of objects as structs with specialized functions that can operate on them. By convention, the inputParser object is stored as the variable p.

p = inputParser;

Step 3: Create the parsing schema

Before we can parse the input, we need to tell our inputParser object what kind of inputs to expect. We do this using the functions:

  • addRequired (to add required positional arguments), and
  • addParameter (to add optional arguments that are specified using 'ArgumentName', value pairs and can be supplied in any order after the positional arguments).

If you are using Octave, the equivalent of the addParameter function is the addParamValue function.

The addRequired function takes 3 arguments:

  1. The inputParser object you are working with
  2. The name of the argument (used internally by the inputParser, not to be used in the function call)
  3. (Optionally) A handle to a validation function which accepts 1 input (i.e., the argument), and returns true, false, or throws an error. This function is used to determine whether the value supplied for that argument is acceptable to be used in the function (known as validation). Anonymous function declarations are permittted in this context.

The addParameter function takes 4 arguments:

  1. The inputParser object you are working with
  2. The name of the argument, which is> to be used in the function call
  3. The default value for that argument. This value is used if the argument is not supplied when the function is called.
  4. (Optionally) A handle to a validation function (with the same properties as outlined for the addRequired function).

Let use these function to create the input schema for our variance function. The order of these functions is important: all uses of the addRequired function must preceed any uses of the addParameter function.

addRequired(p, 'data', ...
            @(x) isnumeric(x) && length(size(x))==2 && (size(x,1)==1 || size(x,2)==1))
addParameter(p, 'RemoveMissing', false, @islogical)
addParameter(p, 'VarianceType', 'population', ...
           @(s) ismember(s, {'population','sample'}))

Step 4: Parse the inputs

Now that our schema is defined, we can parse our inputs using the aptly named parse function. We supply the parse function with the inputParser object, and all the arguments specified in the function definition. Remember to expand the varargin cell array with {:}!

parse(p, x, varargin{:})

Step 5: Use the inputs

The arguments and their results are then stored in the Results field of the inputParser object, which is a struct. You can access the value of each argument using the name you gave it in the call to addRequired or addParameter.

For example, we would access the data argument like this:

p.Results.data

Putting it all together

Now, lets write the actual variance function:

function [var] = variance(x, varargin)
    % Instantiate inputParser
    p = inputParser;

    % Setup parsing schema
    addRequired(p, 'data', ...
                @(x) isnumeric(x) && length(size(x))==2 && (size(x,1)==1 || size(x,2)==1))
    addParameter(p, 'RemoveMissing', false, @islogical)
    addParameter(p, 'VarianceType', 'population', ...
               @(s) ismember(s, {'population','sample'}))

    % Parse inputs
    parse(p, x, varargin{:})

    % For fun, show the results
    disp(p.Results)

    if p.Results.RemoveMissing
        x = x(~isnan(x));
    end

    if strcmp(p.Results.VarianceType, 'population')
        n = numel(x)-1;
    else
        n = numel(x);
    end

    SS = sum((x - mean(x)).^2);
    var = SS/n;
end

Now, we'll call it with the following arguments:

variance([1:10, NaN], 'VarianceType', 'sample', 'RemoveMissing', true)
             data: [1 2 3 4 5 6 7 8 9 10 NaN]
    RemoveMissing: 1
     VarianceType: 'sample'


ans =

    8.2500

If we leave off the 'VarianceType' and 'RemoveMissing' arguments, it will use their default values. Since there was a missing value in the data, and the default is to not remove missing values, the answer ends up being NaN

variance([1:10, NaN], 'VarianceType', 'sample', 'RemoveMissing', true)
             data: [1 2 3 4 5 6 7 8 9 10 NaN]
RemoveMissing: 0
VarianceType: 'population'


ans =

NaN

If we call it with bad input, we'll get a validation error:

variance([1:10, NaN], 'VarianceType', 'asdfsa')
Error using variance (line 157)
The value of 'VarianceType' is invalid. It must satisfy the function: @(s)ismember(s,{'population','sample'}).

Further Reading

I hope this has inspired you to use an inputParser for the next function you write that needs named arguments, flexible ordering, and easy default values!

For more information about working with the inputParser class, checkout Mathworks documentation