indexed object dot notation method gives scalar property
Asked Answered
S

1

14

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?

Speleology answered 7/7, 2013 at 20:14 Comment(7)
I cannot reproduce this; works fine over here...What version of MATLAB are you using?Erythropoiesis
2013a the same "issue". But this works [A([1 2]).data]Konstanz
@Speleology why is your class defined is such a strange way, no constructor?Konstanz
@Konstanz from here, "MATLAB supplies a constructor that takes no arguments..."Speleology
2013b has the same issue. One interesting observation is that A.procData.('data') also returns a scalar.Sr
Wow, this is a very buggy corner of Matlab. Also affects handle classes. Even more interesting: [A.procData().data] accesses illegal memory and can throw a segfault (Mac R2013a). I think the return values are garbage from a buffer overflow; sometimes it returns [1,1] but sometimes it's [1,4e-309] or other such gibberish. Also happens in the simpler case, where the processing is inside the for loop. This is most definitely a Mathworks bug, and a bad one, at that.Uella
The only real solution here is to submit a bug report to Mathworks. Beyond that, the workaround in the question itself is the solution.Uella
W
14

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...

Whitby answered 9/9, 2013 at 21:4 Comment(6)
What fascinates me about this is that Mathworks has gone through the trouble to implement tab completion for these sorts of cases. They actually run the method quite a few times when you hit tab to determine the output type. If you make MyClass extend handle, you can actually get it to process the data without ever hitting enter -- just hit tab! (You can also see this by setting a breakpoint or printing some output and attempting tab completion.) This is crazy, but I think it may have something to do with their implementation of dependent getters.Uella
I suppose you are right, it's like they are encouraging this syntax by implementing tab completion where they shouldn't.. It gets worse if you decide to overload the subsref function for your class (while dealing with scalar and object arrays), which I think is the real source of this mess :)Whitby
Here is a related question where the OP asks to take advantage of this syntax to enable BDD-style unit-testing: expects(1+1).toBe(2). One of the suggested solutions was to implement subsref for the class, but as was warned, this will most definitely break other type of subscripted indexing.. Overloading subsref and subsasgn while using object arrays is almost impossible to get right, not while maintaining all other functionalities provided by default (it can get pretty unwieldy with expressions such as: obj(2).x{3}.y(4:6))Whitby
Excellent response, @Amro. I just want to add that while it is true that a variable must be captured before it can be indexed, you can use a functional-style method to get inline indexing: in = @(x,varargin) x(varargin{:}); in(magic(3), 1, 2);Syncretize
@GordonBean: thanks. Yes there are many ways to get this inlined indexing, see this question for moreWhitby
@Whitby Thanks for the comprehensive answer. Definitely deserves the bounty :-)Sr

© 2022 - 2024 — McMap. All rights reserved.