Function Syntax Design Conundrum

I love using the arguments block, but it doesn't handle this use case well.

I have a function syntax design conundrum, and it's related to the MATLAB function argument block capabilities, which were introduced in R2019b. Specifically, my puzzle is about designs where an input argument can be either a numeric quantity or some kind of option string.

The MATLAB function dateshift has a syntax that fits this pattern:

t2 = dateshift(t,"dayofweek",dow)

The input argument dow can be an integer from 1 to 7. The value 1 indicates Sunday; the value 2 indicates Monday; and so on.

Or, the input argument dow can be either "weekday" or "weekend". For example, dateshift(t,"dayofweek","weekend") returns the next date on or after t that falls on a weekend.

These two forms are different ways to express a certain day of the week. I like this kind of flexibility in how an input value can be specified, and it fits well with decades-long function syntax design practice in MATLAB.

I've been thinking about this pattern because I want to use it for a new function I'm working on. (It will be the subject of a future blog post, I'm sure.)

Here's my conundrum. I've been designing and implementing MATLAB functions for 37 years, and so I am familiar with multiple generations of MATLAB language features for implementing function syntaxes. The latest round, the function arguments block, was introduced in R2019b.

I love using the arguments block, and I never want to go back to any of the old methods for implementing function input argument behavior. Using the arguments block greatly reduces the amount of code needed, and it encourages MathWorks-written and user-written functions to behave in consistent ways, with consistent error messages, many of which are automatically localized in various languages.

However, it just doesn't handle this use case well. I'd like to encourage MathWorks to fill this gap.

Let's see how the arguments block can work for a couple of simplified versions of dateshift.

Arguments block example 1

Suppose I'm writing a function, dayofweek_shift_v1, that takes a number indicating the day of the week, like this:

t2 = dayofweek_shift_v1(t,dow)  % dow can be 1, 2, ..., 7

I might implement that as follows:

function t2 = dayofweek_shift_v1(t,dow)
  arguments
    t  datetime
    dow (1,1) double {mustBeInteger, mustBeInRange(dow,1,7)}
  end

  ...

end

With barely any code, I get all this automatic argument validation behavior:

image_0.png

Arguments block example 2

Here's a second version of the simplified function. Instead of a number, it take an option string, either "weekday" or "weekend".

function t2 = dayofweek_shift_v2(t,dow)
  arguments
    t  datetime
    dow (1,1) dowChoice
  end

  ...

end

dowChoice is an enumeration:

classdef dowChoice
  enumeration
    weekday
    weekend
  end
end

And here is more automatic argument validation behavior:

image_1.png

I love the expressiveness of the very small amount of code. I love the function behavior provided automatically by MATLAB. And I love being able to write the body of the function without worrying about the validity of the input arguments.

But what if I want to allow dow to be either form, as in dateshift? The arguments block doesn't really support this case. The only solution I have found that allows this flexibility and still provides some of the automatic behavior involves writing some helper local functions and then using try-catch. It's ... not great.

Unhappy arguments block example

In this implementation, a local function, with an arguments block, is written for each form of the input. An if-branch in the main body is written so that input validation error messages make the most sense based on the form of the input argument. Try-catch blocks are used in the main body that the local helper functions are not exposed in a confusing way in error messages.

function dayofweek_shift_v3(t,dow)
    arguments
        t  datetime
        dow
    end

    if isnumeric(dow)
        try
            dow = integerDOWChecker(dow);
        catch e
            throw(e);
        end
    else
        try
            dow = stringDOWChecker(dow);
        catch e
            throw(e);
        end
    end

    % ...

end

function dow_out = integerDOWChecker(dow_in)
    arguments
        dow_in (1,1) double {mustBeInteger, mustBeInRange(dow_in,1,7)}
    end

    dow_out = dow_in;
end

function dow_out = stringDOWChecker(dow_in)
    arguments
        dow_in (1,1) dowChoice
    end

    dow_out = dow_in;
end

This is way too much code, and the code requires way too much MATLAB knowledge and experience to write. And, sadly, the results aren't good.

image_2.png

Notice that the error messages above have the incorrect input argument position.

Where is this feature going?

I wish I understood better what to expect about the future evolution of the arguments block. It would help me with future function designs.

In the meantime, I really want to use this particular input argument pattern in the function I'm currently writing. The alternative syntax designs that I've considered seem awkward and unexpressive in comparison. So, I will still use an arguments block, but I will have to write my own validation code for that particular input.