Skip to content

Argument handling

Peter Corke edited this page Jun 22, 2020 · 2 revisions

Argument handling in MATLAB

MathWorks have evolved the way they handle optional parameters. It used to be additional parameters to the function and handled by varargin and nargin. It was often difficult to remember the order and meaning of additional parameters.

They later standardised on keyword-value pairs, where the keyword is a string. For example:

plot(x, y, 'Color', 'red', 'LineWidth', 2)

where the keyword is a character array (single quotes) or a string (double quotes).

Code support in MATLAB was originally quite sketchy but from 2007 there is inputParser which is a bit like Python's argparse for command line arguments.

Since their support was sketchy I wrote tb_optparse to do the job, but it's not strict about keyword-value pairs. In addition it lets you handle keyword-only options like:

R = rotx(30, 'deg')

that can have a negative form like:

R = rotx(30, 'nodeg')

or enum like form

R = rpy2r(angles, 'xyz')
R = rpy2r(angles, 'zyx')

Full details are given at the end of this page. The arguments and their defaults are expressed quite concisely using a MATLAB struct.

Python

Python allows both positional or keyword based arguments, and distinguishes between the two. Consider the case of Link. Using just positional arguments it would be

Link(0, 1, 2, 3)

which makes it difficult for the reader to know what's going on. It also makes it difficult to handle defaults.

Using keyword-value arguments, in MATLAB or Python, would be:

Link('d', 0, 'theta', 1, 'alpha', 2, 'a', 3)

which is easy to read, but a bit cumbersome to write. For MATLAB tb_optparse makes it easy to handle the arguments.

function Link(varargin)
    opt.d = 0
    opt.theta = 0
    opt.alpha = 0
    opt.a = 0
    opt = tb_optparse(opt, varargin)

and for Python we could convert the argument into a list of keyword-value tuples by:

def Link(*pos):
    kwval = [(pos[i], pos[i+1]) for i in range(0, len(pos), 2)]

and then process the tuples.

The Pythonic way would be to use named arguments

Link(d=0, theta=1, alpha=2, a=3)

We could handle both keyword-value and named arguments with a signature like

def Link(*pos, d=0, theta=0, a=0, alpha=0):

but it would require a lot of logic to work out which types of arguments are being passed, to handle malformed or mixed parameter specifications etc.

We need to make a decision on RTB-M compatibility (a good thing) with ugly code and non-Pythonic expression.

We should decide this relatively early. Do we:

  • cut the cord
  • keep the cord for a few select functions, but then what's the algorithm to choose those?
  • keep compatibility for all functions
  • in the spirit of 2to3 do we make the mother of all awk scripts to do this for users

An approach to compatibility

Consider a class MyClass which mimics some class in MATLAB but has Pythonic keyword arguments. We could create a MATLAB compatible version of this from a matlab package which defines the same class but with MATLAB style arguments

matlab/MyClass.py:

import MyClass as mc

class MyClass

    def __init__(self, *varargs):
        # parse the arguments in varargs to a dict kwargs
        
        self.pythonic = mc.MyClass(**wkargs)

    def method1(self, *varargs):
        # parse the arguments in varargs to a dict kwargs
        
        return self.pythonic.method1(**wkargs)

We could probably also do something funky with decorators...

Partial documentation for tb_optparse

% The software pattern is:
%
%         function myFunction(a, b, c, varargin)
%            opt.foo = false;
%            opt.bar = true;
%            opt.blah = [];
%            opt.stuff = {};
%            opt.choose = {'this', 'that', 'other'};
%            opt.select = {'#no', '#yes'};
%            opt.old = '@foo';
%            opt = tb_optparse(opt, varargin);
%
% Optional arguments to the function behave as follows:
%   'foo'              sets opt.foo := true
%   'nobar'            sets opt.foo := false
%   'blah', 3          sets opt.blah := 3
%   'blah',{x,y}       sets opt.blah := {x,y}
%   'that'             sets opt.choose := 'that'
%   'yes'              sets opt.select := 2 (the second element)
%   'stuff', 5         sets opt.stuff to {5}
%   'stuff', {'k',3}   sets opt.stuff to {'k',3}
%   'old'              synonym, is the same as the option foo
Clone this wiki locally