How to fix property validation and its implicit default values?

34 views (last 30 days)
I have a simple class that should create a new figure and plot something on it.
classdef MyClass
properties
Window (1, 1) matlab.ui.Figure
Axes (1, 1) matlab.graphics.axis.Axes
end
methods
function obj = MyClass()
obj.Window = figure();
obj.Axes = axes(obj.Window);
x = -pi:0.01:pi;
plot(obj.Axes, x, sin(x));
end
end
end
The properties are resticted in class and dimension. This is what I want.
My problem is, that a restriction in both class and dimension does force a default value which satisfies them. The result is, that I get two figures (when I execute it first time – more on this later). The first from the implicit default value, the second from the constructor.
This means that I can never restrict dimension and class for types whose construction requires arguments passed to the constructor.
Question 1: Is there a way to construct default values with arguments passed to the constructor call?
In the case of a figure, there seems to be a simple solution to the problem. Since I need a new default figure anyway, I can omit the explicit create in the constructor and directly use the implicitly created default figure.
classdef MyClass
properties
Window (1, 1) matlab.ui.Figure
Axes (1, 1) matlab.graphics.axis.Axes
end
methods
function obj = MyClass()
% use the implicitly created figure here
obj.Axes = axes(obj.Window);
x = -pi:0.01:pi;
plot(obj.Axes, x, sin(x));
end
end
end
Unfortunately, a new problem arises immediately. A note in the documentation mentions that default values for properties are only created once when they are first used.
For reference types like figure, this means that we get the same window in every class instance. If the figure is closed, Matlab of course complains when executing the class again that the object has already been deleted:
>> MyClass
Error using axes
Cannot set property to a deleted object
Error in MyClass (line 9)
obj.Axes = axes(obj.Window);
A workaround is to immediately delete the figure implicitly created the first time it is executed:
classdef MyClass
properties
Window (1, 1) matlab.ui.Figure
Axes (1, 1) matlab.graphics.axis.Axes
end
methods
function obj = MyClass()
% Workaround
if isvalid(obj.Window)
delete(obj.Window);
end
obj.Window = figure();
obj.Axes = axes(obj.Window);
x = -pi:0.01:pi;
plot(obj.Axes, x, sin(x));
end
end
end
Question 2: Is there a better way to validate the dimension and class of a property on the one hand and not have default values before the constructor on the other?
Proposel
If there is currently no general solution to these two problems, I would propose the following:
  • Allow [] as always valid property default value. (It must be explicitly defined!)
  • Within the constructor, the current validation rules apply. ([] must not be assigned)
  • If properties still have the value [] after the constructor has been executed, an exception is thrown.
The proposal is fully backward compatible, but may lead to minor speed reductions during constructor execution. (Depends on the current implementation.)
Now the class can be written as follows:
classdef MyClass
properties
% set default value explicitly as empty (proposal!)
Window (1, 1) matlab.ui.Figure = []
Axes (1, 1) matlab.graphics.axis.Axes = []
end
methods
function obj = MyClass()
obj.Window = figure;
obj.Axes = axes(obj.Window);
x = -pi:0.01:pi;
plot(obj.Axes, x, sin(x));
end
end
end
  3 Comments
per isakson
per isakson on 3 May 2020
Edited: per isakson on 3 May 2020
BUMP: These questions deserve to be answered, i.e. I 'm interested;)
Benjamin Buch
Benjamin Buch on 18 Jun 2020
Edited: Benjamin Buch on 18 Jun 2020
> I am unsure about having " [] " as always valid for default value: each classes should have their own default values.
Its true that "[]" has the disadvantage that it is always of class double. An alternative could be some complety new kind of generic NULL value that can be assigned to any class until the constructor finished.
> Have you tried adding "if ~nargin; return; end" at the beginning of your MyClass constructor?
I don't understand what that's supposed to do. In any case, it can just be another workaround that cannot solve the actual problem. The problem must be solved in the property definition and not in the constructor. The object creation happens before the constructor, so at this point it is already too late to prevent the creation of objects (especially of figure).
It would make me very happy if the problem was finally tackled.

Sign in to comment.

Answers (2)

Sean de Wolski
Sean de Wolski on 18 Jun 2020
Edited: Sean de Wolski on 18 Jun 2020
In >=19b this can be elegantly managed with arguments in the constructor (or with inputParser in older releases).
classdef MyClass
properties
Window (1, 1) matlab.ui.Figure
Axes (1, 1) matlab.graphics.axis.Axes
end
methods
function obj = MyClass(opts)
% MyClass('Name', value)
arguments
opts.Figure(1, 1) matlab.ui.Figure = gcf % or figure() for new
end
obj.Window = opts.Figure;
obj.Axes = axes(obj.Window);
x = -pi:0.01:pi;
plot(obj.Axes, x, sin(x));
end
end
end
The reasoning that I got from development when I ran into the same issue was this:
"Evaluation of property default values occurs only when the value is first needed, and only once when MATLAB first initializes the class. MATLAB does not reevaluate the expression each time you create an instance of the class."
Class initialization does not occur everytime you create an instance of your class. Which is why you see the described behavior. Assigning the property in the constructor is what you should do if you want the property value to be updated everytime you construct an instance. Constructors are used to do that kind of per-instance computation.
If MATLAB worked the other way, there would have to be much more work done to create every instance because default values would have to be constructed rather than copied (usually using very efficient shared-copies) and there would be no good way to tell when a default value was an actual constant that can be documented as a default value versus when the value is computed from an algorithm that depends on the specific circumstances of a particular object's creation.
  1 Comment
Benjamin Buch
Benjamin Buch on 18 Jun 2020
Unfortunately this does not solve the problem.
With "figure" two figures are created, one during the first instantiation of the class, i.e. when processing the properties, and a second within the constructors argument section.
If "gcf" is used, the figure is also created during the first instantiation of the class and is shared by all instances.
This is exactly the same situation as without the arguments section.

Sign in to comment.


J G
J G on 25 Jan 2024
Edited: J G on 25 Jan 2024
Hi,
I had a similar problem with property validation creating undesired default values. See my original discussion on the topic here: https://www.mathworks.com/matlabcentral/answers/1840668-class1-as-property-of-class2-class1-property-initialization#comment_3040526
My solution was to use the built in validation function MustBeScalarOrEmpty which achieved my goal of enforcing a scalar value, but allowing an empty version of the class to be the default. So for your use-case, modify as follows:
classdef MyClass
properties
Window matlab.ui.Figure {mustBeScalarOrEmpty}
Axes matlab.graphics.axis.Axes {mustBeScalarOrEmpty}
end
methods
function obj = MyClass(opts)
% MyClass('Name', value)
arguments
opts.Figure(1, 1) matlab.ui.Figure = gcf % or figure() for new
end
obj.Window = opts.Figure;
obj.Axes = axes(obj.Window);
x = -pi:0.01:pi;
plot(obj.Axes, x, sin(x));
end
end
end
The above code will not create two figure windows.

Categories

Find more on Interactive Control and Callbacks in Help Center and File Exchange

Products


Release

R2018b

Community Treasure Hunt

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

Start Hunting!