indexed object dot notation method gives scalar pr

2019-03-11 08:57发布

问题:

I'm seeing an issue when I try and reference an object property after having used a dot notation to apply a method. it only occurs when I try to index the initial object

classdef myclassexample

properties
    data
end    

methods   
    function obj = procData(obj)            
        if numel(obj)>1
            for i = 1:numel(obj)
                obj(i) = obj(i).procData;
            end
            return
        end
        %do some processing
        obj.data = abs(obj.data);
    end
end
end

then assigning the following

A = myclassexample;
A(1).data= - -1;
A(2).data =  -2;

when calling the whole array and collecting the property data it works fine

[A.procData.data]

if i try and index A then i only get a scalar out

[A([1 2]).procData.data]

even though it seems to do fine without the property call

B  = A([1 2]).procData;
[B.data]

any ideas?

回答1:

I would definitely call this a bug in the parser; A bug because it did not throw an error to begin with, and instead allowed you to write: obj.method.prop in the first place!

The fact that MATLAB crashed in some variations of this syntax is a serious bug, and should definitely be reported to MathWorks.

Now the general rule in MATLAB is that you should not "index into a result" directly. Instead, you should first save the result into a variable, and then index into that variable.

This fact is clear if you use the form func(obj) rather than obj.func() to invoke member methods for objects (dot-notation vs. function notation):

>> A = MyClass;
>> A.procData.data       % or A.procData().data
ans =
     []
>> procData(A).data
Undefined variable "procData" or class "procData". 

Instead, as you noted, you should use:

>> B = procData(A):    % or: B = A.pocData;
>> [B.data]

FWIW, this is also what happens when working with plain structures and regular functions (as opposed to OOP objects and member functions), as you cannot index into the result of a function call anyway. Example:

% a function that works on structure scalar/arrays
function s = procStruct(s)
    if numel(s) > 1
        for i=1:numel(s)
            s(i) = procStruct(s(i));
        end
    else
        s.data = abs(s.data);
    end
end

Then all the following calls will throw errors (as they should):

% 1x2 struct array
>> s = struct('data',{1 -2});

>> procStruct(s).data
Undefined variable "procStruct" or class "procStruct". 

>> procStruct(s([1 2])).data
Undefined variable "procStruct" or class "procStruct". 

>> feval('procStruct',s).data
Undefined variable "feval" or class "feval". 

>> f=@procStruct; f(s([1 2])).data
Improper index matrix reference. 

You might be asking yourself why they decided to not allow such syntax. Well it turns out there is a good reason why MATLAB does not allow indexing into a function call (without having to introduce a temporary variable that is), be it dot-indexing or subscript-indexing.

Take the following function for example:

function x = f(n)
    if nargin == 0, n=3; end
    x = magic(n);
end

If we allowed indexing into a function call, then there would be an ambiguity in how to interpret the following call f(4):

  • should it be interpreted as: f()(4) (that is call function with no arguments, then index into the resulting matrix using linear indexing to get the 4th element)
  • or should it interpreted as: f(4) (call the function with one argument being n=4, and return the matrix magic(4))

This confusion is caused by several things in the MATLAB syntax:

  • it allows calling function with no arguments simply by their name, without requiring the parentheses. If there is a function f.m, you can call it as either f or f(). This makes parsing M-code harder, because it is not clear whether tokens are variables or functions.

  • parentheses are used for both matrix indexing as well as function calls. So if a token x represents a variable, we use the syntax x(1,2) as indexing into the matrix. At the same time if x is the name of a function, then x(1,2) is used to call the function with two arguments.

Another point of confusion is comma-separated lists and functions that return multiple outputs. Example:

>> [mx,idx] = max(magic(3))
mx =
     8     9     7
idx =
     1     3     2

>> [mx,idx] = max(magic(3))(4)     % now what?

Should we return the 4th element of each output variables from MAX, or 4th element from only the first output argument along with the full second output? What about when the function returns outputs of different sizes?

All of this still applies to the other types of indexing: f()(3)/f(3), f().x/f.x, f(){3}/f{3}.

Because of this, MathWorks decided avoid all the above confusion and simply not allow directly indexing into results. Unfortunately they limited the syntax in the process. Octave for example has no such restriction (you can write magic(4)(1,2)), but then again the new OOP system is still in the process of being developed, so I don't know how Octave deals with such cases.


For those interested, this reminds me of another similar bug with regards to packages and classes and directly indexing to get a property. The results were different whether you called it from the command prompt, from a script, or from a M-file function...