Deprecate assignment as an expression?

With the 2021a release, Matlab now allows functions to be called with the syntax:

some_fun (name_a = value_a, name_b = value_b)

See the description here: Function Argument Validation - MATLAB & Simulink and in the “Name=Value Syntax” entry under the “Language and Programming” topic of the 2021a release notes here: Release Notes for MATLAB - MATLAB & Simulink

Briefly, the syntax in Matlab is similar (but not identical?) to using

some_fun ("name", value)

With an arguments block, you can do this:

function some_fun (namedargs)
  arguments
      namedargs.name_a ... %% type/size restrictions
      namedargs.name_b ... %% and default values
  end
  ...
end

Because assignments are expressions in Octave, it has always allowed the calling syntax but the meaning is much different. In Octave,

some_fun (name_a = value_a, name_b = value_b)

assigns value_a to name_a and value_b to name_b in the scope of the function call and then passes the values as normal arguments to the function. It doesn’t associate values with named variables (or structure fields) in the function that is called.

It seems to me that supporting this new feature will mean that we will have to change the meaning of assignments so they are statements (as in Matlab) and not expressions that produce values. We could try to change the meaning only in the context of an expression indexed with '()' but I’m not sure that the trouble is worth the inconsistency.

I’m pretty sure Matlab just does these things because they’ve run out of worthwhile modifications, but still feel like they need to make changes every 6 months so they can justify a new release and their continued license fees.

When would this syntax be used outside of parentheses? In Octave and Matlab this is an error already

(x = 1, y = 2)
error: parse error:

  syntax error

>>> (x = 1, y = 2)
          ^

It seems like it would be possible to restrict to being inside parentheses by hooking any new code in to where Octave currently errors.

Question, how would default input arguments work if Octave changed from expressions to statements? Sample code:

function tst_fcn_def_arg (x = 1, y = 2)
  printf ("arg 'x': %d\n", x);
  printf ("arg 'y': %d\n", y);
endfunction

This correctly prints 1 for x and 2 for y.

I’m tentatively a fan of supporting the new name = value named argument function invocation syntax. I think this makes for more concise, readable function call site code for functions which have many arguments, especially when many of those arguments are optional.

At my work with Matlab, I end up with functions that start out with 2 or 3 arguments, and over time they evolve to add a bunch of options which tweak their behavior. By convention, we (and other M-code I’ve seen) uses [] as a placeholder to mean “do the default”. So we end up with function calls like this:

my_fancy_plot(x, y, 1.2, [], [], [], true, [], [], false)

Who can remember what all those positional parameters are? I have to actually count their positions out. With name = value support, you could have something like this:

my_fancy_plot(x, y, 1.2, condense_overlapping = true, gradient_fill = false)

You can get similar behavior with 'name', value, ... arguments, but this new syntax may well work better with arguments validator blocks and end up being more concise to write definitions for, without having to fall back to argumentParser or whatever it is, or manual argument list parsing.

I have no opinion on changing whether assignments are expressions outside the context of a (...) indexing/function-call expression.

Expressions like (x = 1, y = 2) fail because multiple expressions can’t appear inside parentheses unless they are part of an index expression like fcn_or_var (x = 1, y = 2). But in that context, supporting the new meaning of named argument values is different from the current meaning of assignment in the calling context.

After looking at the parser again over the last few days, I remember that we already do have separate rules for parameter lists. But given the possibility of eval, evalin, assignin, or load to insert variables into the workspace, there is no way to know from syntax alone whether a given index expression is a function call or variable reference, so restricting the behavior to function call parameter lists doesn’t seem possible. Or has Matlab changed so that it is possible to distinguish variables from functions from syntax alone?

Default argument values in function definitions should still be possible. I don’t think it creates a conflict with the new Matlab arguments block, but now we have two ways of doing the same thing.

I agree that the new syntax for named arguments is better than "name", value pairs.

So how do we get from the current meaning of var = value to the new one?

So how do we get from the current meaning of var = value to the new one?

And here we get to the part that is well above my level of expertise; I don’t think I have much to contribute there…

Well above my level of expertise too, but my question is what do we lose (or what will have to change) if assignments are changed to be interpreted as statements instead of expressions? Would it be that the following syntax is not allowed any more (as in Matlab)?

a = b = 1;

Are there other examples?

I made the attached change to find assignment expressions that appear inside index expression argument lists in the Octave sources and found the following instances by executing the __parse_file__ function for each .m file in the scripts directory of the source and build trees.

assign-expr-diffs.txt (2.3 KB)

The results are:

scripts/io/importdata.m, line 174:
  while (ischar (row = fgetl (fid)))

scripts/path/private/getsavepath.m, line 40:
    while (ischar (line = fgetl (fid)))

scripts/testfun/test.m, line 933:
    while (ischar (ln = fgets (fid)))

scripts/miscellaneous/private/display_info_file.m, line 57:
  while (ischar (line = fgets (fid)))

scripts/miscellaneous/publish.m, line 682:
    if (! isempty (fname = regexpi (strjoin (block, ""),

scripts/miscellaneous/publish.m, line 694:
    if (! isempty (fname = regexpi (strjoin (block, ""),

scripts/statistics/quantile.m, line 427:
  if (any (k = find ((p >= 0) & (p <= 1))))

scripts/pkg/private/configure_make.m, line 195:
    while (ischar (ln = fgets (fid)))

scripts/plot/util/struct2hdl.m, line 354:
    if (! isempty (idx = find (oldax == p)))

I even wrote one interactively while I was running the check:

fid = fopen ("files", "r");
while (ischar (file = fgetl (fid))) __parse_file__ (file); endwhile

LOL, I guess this isn’t something rare, at least for me, and I would probably be (at least slightly) annoyed if Octave responded with a syntax error about invalid use of an assignment statement in this case.

Now that Matlab accepts this syntax (at least for functions that have arguments blocks that can handle name, value arguments), what does it do if you call a function with this syntax when it doesn’t accept name, value arguments? Is there any difference in the error for that case vs. an invalid name used in a function that does accept name, value arguments?

Note that I didn’t find any cases in the Octave sources that appear to be variable indexing, though that is also allowed since assignments in Octave are just expressions that produce values.

We could possibly continue to allow chained assignment expressions outside of argument lists, but the behavior inside parameter lists would have to change. I’m thinking that having different behavior could be confusing, but maybe people are used to that kind of behavior. For example, in Python you can write a = b = 1 but fcn (named_argument = value) does the same kind of name, value argument passing that was recently added to Matlab, correct?

>> pinv (A = 1)
Error using svd
First input must be single or double.

It puts string “A” as a first input.

Do you mean something like (a = eye (2))(4)?

Thanks for checking.

I meant any variable indexing expression like A(idx = 1) to select the first element and also assign 1 to the variable idx.

One possible path forward is to handle foo (var = val) as Matlab does now, passing a name, value pair as the index for foo (whether it is a variable or function) and allowing the old behavior IFF the assignment expression itself is enclosed in parenthesis: foo ((var = val)). If we see foo (var = val), we can issue an optional warning that the behavior has changed and link to an explanation that shows how to recover the previous behavior by surrounding the assignment in parentheses.

I found some more examples in the database, generate_html, miscellaneous, msh, optim, parallel, struct, and video Forge packages. Only a simple search for the regular expression (if|while).* = . So quite a few package developers find it useful to initialize an assignment in a conditional expression.

One that jumps out in particular is a pattern like this, also cited in your search results above:

if (isempty (value = some_value_or_function_call))
  ...

which would be evaluated as a keyword function argument under the Matlab parser. This pattern is used quite a bit in several different Forge packages, I counted about 50 instances.

Assignments by themselves in conditional expressions like

if (value = some_value_or_function_call)
  ...

wouldn’t be affected. It’s only the ones like you showed where the assignment happens inside an index expression.

At least fixing this could be done now because

if (isempty ((value = some_value_or_function_call)))

is already valid syntax.

If we decide to not provide the compatible behavior, then I expect we will see an increasing number of bug reports about this feature, same as for the string class and the behavior of double-quoted strings.

Right, understood. I always forget function calls are also “index expressions.” :sweat_smile:

I think this change in favor of compatibility is a good thing, and the extra parentheses is a nice simple workaround to keep the previous behavior, it’s all good. Just pointing out that this will probably end up surprising some package maintainers. Like you said earlier, clear diagnostic messages will be very helpful.

Could someone test the following in the latest Matlab release (one is new enough to support using keyword = value to pass"keyword", argument pairs to functions)?

x = (1:256)';
abs ('b')
x(abs ('b'), 1)
x("b", 1)
x(b = 1)

There may be errors, so please execute each line individually at the command prompt.

I’m trying to understand whether x(b=1) is converted to x('b', 1) or x("b", 1) for both function calls and variable indexing or whether something else is happening.

With Matlab R2021a:

>> x = (1:256)';
>> abs ('b')

ans =

    98

>> x(abs ('b'), 1)

ans =

    98

>> x("b", 1)
Unable to use a value of type string as an index.
 
>> x(b = 1)
Unable to use a value of type string as an index.
 
>> 

Thanks.

Oh, I just noticed I forgot to ask about

x('b', 1)

But I think that will be the same as

x(abs('b'), 1)

If so, then it looks like b = 1 is converted to a string object and value pair at the time of evaluating the argument list, prior to the function lookup step. If necessary, I suppose we could also dig into that a little deeper by defining a class object that derives from string and look at what is called for overloaded functions.

I propose that we start by issuing a warning when Octave performs argument list evaluation and finds an element of the argument list that is a simple assignment expression. The warning can say that the behavior will change in a future version and that to preserve the current behavior you’ll need to surround the assignment in parentheses.

When we make the change to support this new function call feature, we can convert these assignments in argument lists to be (string, value) pairs and proceed as before.

This also provides some additional incentive to have a string object in Octave.

Yes, x('b', 1) and x(abs('b'), 1) are the same.

As a confirmation with functions:

function testme(varargin)
    varargin
end
>> testme(a=1, b=2)
varargin =
  1x4 cell array
    {["a"]}    {[1]}    {["b"]}    {[2]}

As you point out, the conversion happens early on:

function testme(one_input)
end
>> testme(a=1)    
Error using testme
Too many input arguments.

Thanks for testing. So the "name = value" syntax really is just syntactic shorthand for a ("name", value) argument pair.