[Contents] [TitleIndex] [WordIndex

1. Why using key=value syntax to manage input arguments is not a good idea

Abstract

The purpose of this example is to show why using the key=value syntax to provide optional arguments may be a poor programming practice. It can lead to difficulties to produce new releases of the software and can lead to difficult bugs. A safer approach, based on the empty matrix, is suggested.

1.1. The key=value syntax

In this section, we present a method to provide optional arguments based on the key=value syntax. This method makes use of the exists function. In the first part, we analyze the method to implement this syntax. In the second part, we analyse the benefits and the drawbacks of this method.

1.1.1. How to implement the key=value syntax

Consider the following simple function.

function y = myfunction1 ( x, a, b, c )
  y = a*x^2+b*x+c
endfunction

It is straightforward to use this function:

-->myfunction1 ( 1, 1, 3, 1)
 ans  =
    5.  

It may be more convenient if the arguments a, b and c were optional.

A method which seems to be interesting is the key=value syntax. In order to implement this method, we use the exists function. By default, the arguments a, b and c are all set to 1.

function y = myfunction2 ( x, a, b, c )
  if ~exists("a","local") then
    a = 1
  end
  if ~exists("b","local") then
    b = 1
  end
  if ~exists("c","local") then
    c = 1
  end
  y = a*x^2+b*x+c
endfunction

There are now several ways to call this function. We can call it without specifying the values of a, b or c, which are then set to their default values:

-->myfunction2 ( 1 )
 ans  =
    3.  

In the previous example, the variable a, for example, is undefined when the body of the function myfunction2 is executed. Therefore, the statement exists("a","local") returns false, so that the associated if block is executed.

We can also specify just one argument, with the key=value syntax.

-->myfunction2 ( 1, b=3 )
 ans  =
    5.  

In the previous example, the variable b is defined in the function myfunction2, so that the statement exists("b","local") returns true. Therefore, the associated if block is not executed: the value b=3 is then used, as expected. All in all, we set b, but skip a.

1.1.2. Benefits and drawbacks

The previous approach as several advantages:

But there are also several significant drawbacks:

One possible bug is when the user uses a wrong variable name, which is ignored by the function.

-->myfunction2 ( 1, d=3 )
 ans  =
    3.  

Here, the variable d is just ignored by the function, but the user does not get an error message: this silently fails. The previous example is simple, but suppose that the variable name is "H_form", but the user writes "Hform": would someone notice the bug ?

1.2. An implementation based on the empty matrix

In this section, we present a method to provide optional arguments which is both simple and safe. It is based on the varargin variable and the empty matrix.

The method that we suggest here is presented in more depth in [1].

We introduce the following function which is a simplified implementation of apifun_argindefault from the apifun module [2]. The function takes as input arguments the list of arguments vararglist, the variable index ivar and the default value default.

function argin = argindefault ( vararglist , ivar , default )
    rhs = length(vararglist)
    if ( rhs < ivar ) then
        argin = default
    else
        if ( typeof(vararglist(ivar))== "constant" ) then
            if ( vararglist(ivar) <> [] ) then
                argin = vararglist(ivar)
            else
                argin = default
            end
        else
            argin = vararglist(ivar)
        end
    end
endfunction

We now define an implementation of the function which makes use of argindefault.

function y = myfunction4 ( varargin )
        [lhs,rhs]=argn();
        if rhs<1 | rhs>3 then
                lclmsg = "%s: Wrong number of input arguments: %d to %d expected.\n"
                error(msprintf(gettext(lclmsg),"myfunction4",1,3));
        end
        x = varargin(1)
        //
        a = argindefault ( varargin , 2 , 1 )
        b = argindefault ( varargin , 3 , 1 )
        c = argindefault ( varargin , 4 , 1 )
        //
  y = a*x^2+b*x+c
endfunction

The previous function has the following calling sequence:

 y = myfunction4 ( x)
 y = myfunction4 ( x, a)
 y = myfunction4 ( x, a, b)
 y = myfunction4 ( x, a, b, c)

Moreover, any optional argument is replaced by its default value. The simplest example is with all optional arguments set to their default value:

-->myfunction4 ( 1 )
 ans  =
    3.  

We can also set the argument b, and still use the default value of a.

-->myfunction4 ( 1, [], 2)
 ans  =
    4.  

1.3. Conclusion

The key=value syntax is a simple method to provide optional arguments to a function. Still, it has several drawbacks which make it a difficult programming method.

On the other hand, the method based on varargin and the empty matrix is a safe programming practice. Although it is slighly more complicated at first glance, the argindefault function makes it as simple as the key=value syntax: but it is much safer.

There is a third method to manage optional, non-positional, input arguments. This method is based on (key,value) pairs. In the previous example, this method would manage the a, b, c options with the following statemements:

y = myfunction4 ( x)
y = myfunction4 ( x, "a", a)
y = myfunction4 ( x, "b", b)
y = myfunction4 ( x, "a", a, "b", b)
y = myfunction4 ( x, "b", b, "a", a)

With this method, the input arguments can be set without taking into account their position in the calling sequence. Moreover, the function can check that the key is within a pre-defined set of available options: this prevents for errors in the option names. An example of such a management in Scilab is the optimset/optimget function:

A script containing all the previous examples is provided here:

1.4. Acknowledgements

I thank Bruno Pinçon and Allan Cornet for their comments on this document.

1.5. Bibliography


2022-09-08 09:27