Realize argument parsing similar to the "plot" function in Octave
In GNU Octave, the frequently used plot
function has several overloaded versions:
-- : plot (Y)
-- : plot (X, Y)
-- : plot (X, Y, FMT)
-- : plot (..., PROPERTY, VALUE, ...)
-- : plot (X1, Y1, ..., XN, YN)
-- : plot (HAX, ...)
-- : H = plot (...)
The arguments passed to plot
have two main features as below, which make its function calling very flexible.
- The argument list has a variable length.
- After numeric parameters, such as
HAX
,X
andY
, there are multiple ofPROPERTY-VALUE
pairs.
In my previous projects, when I wrote a function requiring different combinations of arguments, I would use varargin
as its formal parameter, which wraps the list of input arguments in a cell array. Depending on the length of varargin
, different parsing schemes were implemented.
function foo(varargin) switch (length(varargin)) case 1: case 2: case 3: endswitch endfunction
As an alternative, I might also explicitly list all the possible parameters in the function’s signature. Then, inside the function body, I used the exist
function to check their availability and assigned default values to the missing variables.
function foo(image_handle, file_name, line_color) if (!exist("image_handle", "var")) ## Assign the default value to @p image_handle with the current figure ## handle. image_handle = gcf; endif if (!exist("file_name", "var")) ## Default image file for saving. file_name = "output.png"; endif if (!exist("line_color", "var")) line_color = "r"; endif endfunction
It is obvious that the first method above requires lots of code to write, if there are many cases to be handled. For the second method, all the parameters are locked to their positions in the function’s signature, which is not flexible. Therefore, a new programming pattern is needed in order to implement a function similar to the internal function plot
.
To solve this problem, Octave’s parseparams
and inputParser
class can be used. As shown in the following example code for plotting support points in a finite element cell, I pass varargin
firstly to parseparams
. It will separate the parameter list into two groups reg
and prop
. reg
stores all the leading numeric arguments, while prop
stores the remaining ones, the first of which should be a string. In my case, there is only one optional leading numeric parameter, which is the figure handle. Therefore, by checking the length of reg
, figure handle can be obtained from the argument or set to the current figure via gcf
.
Next, an inputParser
object p
is created for parsing and extracting both required parameters and parameter-value pairs. Note that each call of the parameter adding member function, like p.addRequired
and p.addParameter
, a function handle is passed as the last argument, which is responsible for user input validation. This is consistent with the spirit of defensive programming and makes our code more robust. Moreover, a default value should be provided for defining a parameter-value pair.
Finally, to run the parser p
, call its member function parse
on the unwrapped cell array prop{:}
. The calling convention of plot_support_points
now becomes consistent with plot
.
function plot_support_points(varargin) ## The function @p parseparams is used for splitting the argument ## list into figure handle part and the remaining part. [reg, prop] = parseparams(varargin); if (length(reg) > 0) h = reg(1); else h = gcf; endif p = inputParser(); p.FunctionName = "plot_support_points"; p.addRequired("data_file", @ischar); val_float = @(x) isscalar(x) && isfloat(x); p.addParameter("offx", 0.02, val_float); p.addParameter("offy", 0.02, val_float); p.addParameter("marker", "o", @ischar); p.addParameter("markersize", 12, val_float); p.addParameter("markerfacecolor", "r", @ischar); p.addParameter("markeredgecolor", "r", @ischar); p.addParameter("labelsize", 18, val_float); p.parse(prop{:}); endfunction