Function Syntax Design Conundrum
December 2, 2024
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:
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:
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.
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.
Reader Comments on this Post
Note from the future: When I original published this post, several readers provided detailed comments. I have now migrated my blog to a different publishing system, but I was not able to migrate the comments. I wanted to preserve them, though, so I have updated the post to include them directly.
John Michael Brown (July 13, 2025)
I’ve recently been digging into this and some tangentially related things. I very much so dislike the idea of not having metadata on functions and the relevant documentation for class methods is disappointing to say the least when compared to that of the rest of the metadata system.
Well I started digging through a bunch of files and logically tracking down where I expected things to be. A big hint is that outside of functions that are using functionSignatures.json files, must be member is unique in its ability to give user defined functions dynamic auto-competition hints. This alone made it pretty clear to me their had to be a more rich metadata system for functions. After digging through an ungodly amount of files I assumed were related to metadata I found a call to “matlab.internal.metafunction” turns out both methods and functions have gorgeous metadata just like everything else (building out a dynamic schema generator for my ollama api want native classes and functions to be able to define structured outputs, then initialize and return matlab native objects to the user). Anyway, that lead to an even stronger desire to figure out the custom auto complete tools.
Eventually I found “matlab.internal.getListOfEnumeratedStrings” this function is responsible for finding the enumerated values but only for properties, they metaproperty has a hidden, but accessible Type property which is rather helpful. Seems as though the argument is pretty similar to properties in most regards so I was hoping there would be a version for arguments, if there is I haven’t found it. That said I continued digging, focusing more on looking for things that were mustBeMember related since it is the one bit of tooling for use to display dynamic autocompletions. I am hoping that it will lead to finding the necessary information to ducking validators into the system, or just general access… I don’t think it will be the latter however. Looking at “matlab.internal.tabular.functionSignatures.keyChoices” this function is meant to generate the completions for dynamic join calls; this was nice but I suspect its likely limited to json usages. I’ve looked a little into the matlab.system and believe it’s likely simulink only from the looks of it. but then there is matlab.internal.validation.OptionalFlag which seems to be involved in the process, haven’t test that this enables auto complete but is another example of undocumented opaque interaction with the argument system.
I agree that at the moment function arguments blocks lack some nice functionality. personally, I would love to see the string vs data that you mentioned here (although most of the times I find myself wanted to lean on this is to match the dim “all” vs an integer). I also find the lack of true flags kind of disappointing. what I would love to see, and am currentlly sort of mimicking in some of my personal code, is using repeating (1, 1) string mustBeMember(~, flagValues) to replicate true flags… this only works of course if you don’t need to implement repeating arguments elsewhere.
for example, I’ll do:
function [C, ia, ic] = unique_(A, options, opts)
arguments
A
end
arguments(Repeating)
options (1, 1) string {mustBeMember(options, ["rows", "stable", "sorted", "first", "last"])};
end
arguments
opts.Dim (1, 1) double {mustBeInteger, mustBePositive} = 2;
end
% Validate the flag inputs
flag_opts = {"rows", ["stable", "sorted"], ["first", "last"]};
flags = validate_flags(options, flag_opts);
where validate_flags is defined as:
function flag_struct = validate_flags(selected_flags, flags)
arguments
selected_flags (1, :) cell;
flags cell = {};
end
% Normalize the input flags to a string array
selected_flags = [selected_flags{:}];
% Normalize no flags so that the ismember call can be used for determining logical flags
if(isempty(selected_flags))
selected_flags = string(missing);
end
% Default is to assume all there are no flag groups are mutually exclusive
if(isempty(flags))
flags = num2cell(selected_flags);
end
% Initialize the arguments for the flag struct
fields = [flags{:}];
args = [num2cell(fields); createArray(size(fields), "FillValue", {false})];
flag_struct = struct(args{:});
% Iterate through groups of mutually exclusive flags determining selection
for n = 1:numel(flags)
% Determines which flags from the group are asserted
asserted_flags = ismember(flags{n}, selected_flags);
asserted_flags = flags{n}(asserted_flags);
% Handles validating mutually exclusive flags
if(numel(asserted_flags) > 1)
% Throw error if multiple mutually exclusive flags are asserted
throwAsCaller(MException( ...
"JB:convert_flags:InvalidFlag", ...
"Mutually exclusive flags asserted; %s are asserted. These flags are mutually exclusive.", ...
listNL(asserted_flags, "'%s'") ...
));
elseif(~isempty(asserted_flags))
% Set the option corresponding to the flag positive
flag_struct.(asserted_flags) = true;
end
end
end
I obviously find this pattern quite nice and would love to see something similar where you could use a flags attribute for the arguments block
arguments(Flags)
flagname1
flagname2
% ...
end
then in the function body you get a variable flags that has fields corresponding to flagnames where their value is a logical corresponding to whether they were asserted. I find that otherwise processing them is rather annoying, hence my validate_flags helper.
interestingly it seems to me as though there may be a desire for richer autocomplete support since the comments for the OptionalFlag class notes:
“This class provides basic optional flags support until we have enums that support text identifiers with special characters in them.”
Now whether users will get access to that is another thing. I do find it were that even mustBeA(a, [“someEnum”, “numeric”]) doesn’t support the context you mention for dow though; I feel like that would be an intuitive approach. Personally, I won’t be satisfied until we have full dynamic StringSets so I can replicate variable/field/properties name autocompletions or other custom enumerated value sets… I’m going to keep poking around until I find something. Id imagine there’s an argument mirrored version of getEnumeratedStringList somewhere. that function uses the hidden meta-property “Type” property I mentioned, but it also heavily relies on the validation metadata which is in the function arguments metadata too so you could theoretically replicate it… only problem is getting that data fed into whatever system uses it for the function’s autocomplete; hence my focus on mustbemember(). Been empty handed the past few days but I don’t plan on stopping the search until something turns up… Ill let you know if it does though. Love the content; your cool as heck <3 Glad to see someone as cool as you loving on the argument blocks as much as i do!
Titus Edelhofer (January 16, 2025)
Hi Steve, you nailed it. I sometimes face exactly this problem, where some sort of “switch casing” with respect to arguments would be helpful. Thanks for the write up!
Grant Cook III (December 2, 2024)
Steve, I agree completely with your sentiments regarding the arguments block in MATLAB—it is amazing powerful, but not capable of all potential use cases.
What do you think of the code below? While it cannot modify the input, as your _v3 function can, it is concise, performs all the mentioned checks, and appropriately points to the argument at position 2 as the culprit.
function t2 = dayofweek_shift_v4(t, dow)
arguments
t datetime
dow {custom_dow_cheker(dow)} = 1
end
t2 = dateshift(t, "dayofweek", dow);
end
function custom_dow_cheker(dow)
if isnumeric(dow)
mustBeScalarOrEmpty(dow);
mustBeInteger(dow);
mustBeInRange(dow, 1, 7);
else
mustBeTextScalar(dow);
mustBeMember(dow, ["weekday", "weekend"]);
end
end