Clear Filters
Clear Filters

Recovering the built-in empty functionality when empty() is overloaded

4 views (last 30 days)
I am trying to create a custom class, say CustomClass, that will redefine parentheses indexing. One of the requirements of parentheses overloading is to overload the empty() method. I would ideally like the new method to continue returning a native empty object of the custom class, exactly as before empty() was overloaded.
I have not been able to find a way to do that. The command builtin('empty',CustomClass) isn't defined. The command empty(CommandClass) simply causes recursion... Nor does something like obj=empty@handle work.
Since there is conceptuably a distinct difference between a fully instantiated copy of the new custom class (even if this reports it's size as zero and itself as empty by overloading those methods too) and a true empty (non-instantiated) copy of the class I am wondering if there is any way to achieve what I need.
For example, a true empty copy of a class returns zero arguments when you access its properties. An instantiated copy returns the property values.
The sample code is:
classdef CustomClassA < handle
properties
prop1
end
methods (Static)
function obj=empty(varargin)
obj=CustomClassA;
end
end
end
classdef CustomClassB < handle
properties
prop1
end
% empty not overloaded
end
Getting the empty instances of both classes results in an instantiated object of CustomClassA and an empty object of CustomClassB
a=CustomClassA.empty
a =
CustomClassA with properties:
prop1: []
b=CustomClassB.empty
b =
0×0 CustomClassB array with properties:
prop1
class(a.prop1)
ans =
'double'
class(b.prop1)
Error using class
Not enough input arguments.
So my question basically is:
What can go in the CustomClassA empty() overload to recover the behaviour of CustomClassB.empty?

Answers (2)

Kyle
Kyle on 3 Nov 2023
Unfortunately, this can't be done using RedefinesParen with default property indexing. When you use the RedefinesParen mixin, you are a scalar object pretending to be an array. In a lot of places, you can pretend very convincingly! But when you pretend to be different sizes, you also pretend to be empty, since "emptiness" is based on your size. So whenever you pass an "array" of your object around, you are still technically passing a scalar object. As such, your properties are referenced as if you are a scalar as well.
However, this is different to the behavior when you index into your object with parenthesis. When you overload paren indexing with RedefinesParen, you can also return a variable number of outputs for paren reference. This depends on how you implement your parenListLength method. So it is possible that you could theoretically design your class so that class(b().prop) matches the behavior of class(a().prop). But you will still not be able to do so for b.prop directly, without referencing with parenthesis first.

chrisw23
chrisw23 on 25 Oct 2023
Edited: chrisw23 on 25 Oct 2023
you have the option to define a constructor with arguments to define the state of the returned object
i.e.
classdef CustomClassA < handle
...
function CustomClassA(<arg>) % constructor
...
function delete(obj) % destructor
  4 Comments
Nicolas Shiacolas
Nicolas Shiacolas on 25 Oct 2023
Hi Walter. Thanks for your input.
Actually what you are saying is not exactly correct, at least on my Matlab release. If you use the statement
b=CustomClassB.empty
where CustomClassB derives from handle and the empty method has not been overriden, as above, a special (empty) class instance seems to be returned. The variable b is acknowledged to be empty and of class 'CustomClassB', but none of the class properties have actually been instantiated or assigned. b is like a reference to the class structure, but not an actual instance yet.
Hence, for example, trying to check the class of the properties fails because the class properties simply don't exist. So the statement
class(b.prop1)
returns an error that it has not been passed any arguments. It appears that accessing a valid property of b is like a null statement, which is ignored if you run it in the command prompt for example; but accessing an invalid property of b returns an error; so the system knows about the structure of b.
Once the class empty method is overloaded, this ability to get the empty, non-instantiated class object seems to disappear. The empty method, once overloaded, is required to return an object. What I am trying to find out is how to return the native empty, non-instantiated class object, such as would be presumably be returned by
obj=builtin('empty',CustomClassB)
(except the function empty is neither builtin, nor does it allow itself to be run as a function, unlike delete).
The statement CustomClassB.empty seems to create a reference to the class without invoking the class constructor. Using any call to the class constructor creates an instantiated object.
What I am trying to achieve is exactly this: how to re-create the native empty class object once I have overloaded the empty method.
Kyle
Kyle on 2 Nov 2023
Hi Nicolas,
Walter is correct. Instantiation has specific mechanics that are not based on when a constructor is called.
To help illustrate things, I am going to try to clarify how empty and RedefinesParen work.
  • empty: In my mental model, there way to think about an empty object is that at least one of its array dimensions is equal to zero. Recall that every variable in MATLAB is an array with dimensions. So, a 0x0 array would be empty, but so is a 2x2x5x0 array. This is true for all objects you define, including those with RedefinesParen. This is also why empty supports a size input. If you want to be thorough, you can tell your empty function to return different empty instances of your class based on what dimensions are passed into the empty method.
  • RedefinesParen: When you use this, I find it helpful to remember that your class is a scalar (1x1) object pretending to be an array of objects. You are in control of the behavior of paren indexing into members of your class. This includes determining the dimensions of your object as well. And since you control the size that gets returned, you control when you pretend to be empty. If your class fits the scalar model, one option is to just inherit from Scalar instead and prohibit empty entirely. If not, you need to determine what "empty" means conceptually for your class, and return someting accordingly.
Now, we can illustrate how they interact. When you redefine paren, you are in control of size, which is how the default isempty will determine what to return for your class. So long as your empty() method returns <an instance of your class where size returns at least one zero dimension>, you will satisfy the requirements.
I do not know what your array-like object, CustomClass does to store the underlying array information. But you could have empty return an empty array of that underlying type however it gets stored. You can see an example of this called ArrayWithLabel in the RedefinesParen help page under "Examples." Alternatively, your class might store the dimensions in an entirely different variable from the data. You could return an instance with no data aside from a dimension of 0x0 or some other "empty" size value.
-----
A completely different question you asked was about "Error using class. Not enough input arguments."
This is because arr.prop returns a comma-separated list of 0 elements (see generating a comma-separated list section). In other words, it has 0 output arguments. class() called with zero arguments will always print this error. Because your first custom class overloads empty, it returns one input argument, which class() knows what to do with.

Sign in to comment.

Categories

Find more on Develop Apps Using App Designer in Help Center and File Exchange

Products


Release

R2023b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!