I have a large set of small, related classes linked together by an interface class. All classes implement a static method, which retrieves and processes data specific to the class.
The output of that static method needs to be formatted in at least 2 ways. Since the transformation of one format to the other is always the same and rather trivial (though long), I thought I'd implement it as a concrete, sealed, static method in the superclass.
However, then I run into the following problem:
% (in Superclass.m)
classdef SuperClass < handle
methods (Static, Abstract)
[arg1, arg2] = subsStaticMethod;
end
methods (Sealed, Static)
function [other_arg1, other_arg2] = supersStaticMethod
% Get data here
[arg1, arg2] = (???).subsStaticMethod
% transform data here
% ...
end
end
end
% (in Subclass.m)
classdef SubClass < SuperClass
methods (Static)
function [arg1, arg2] = subsStaticMethod
% Get class-specific data here
% ...
end
end
end
As far as I can see, calling SubClass.supersStaticMethod() is impossible with this design, because static methods need to be called using the class name explicitly. In other words, there is no way to insert the subclass name instead of the (???) in SuperClass.supersStaticMethod above.
Things I've tried:
mfilename('class') this doesn't work, because this always returns 'SuperClass'
dbstack does not contain the information that the method is actually being called from a subclass
I know that I can work around this problem by making supersStaticMethod non-static, and calling the method on a temporary instance (like SubClass().supersStaticMethod()). Or create a small wrapper method inside each subclass that just calls the superclass method with mfilename('class') as argument. Or any of a 100 other things that seem equally clumsy.
But I'd really like to know if there is some meta.class trickery or something that can cleanly solve this problem. All I've found is this dated thread, which processes the MATLAB command line programmatically to get the subclass name.
However, my classes are to be used inside scripts/functions, and command line use is going to be for debugging purposes only...
Any ideas?
Below's my hacky proposal. The idea is that you store the current calling class in a "static variable" of SuperClass, then query this field and use it in feval to call the correct subclass' method. Several notes:
It could only work as long as you're not doing some computations in parallel (i.e. calling SubClass#.supersStaticMethod from more than one thread at a time under a shared memory architecture - in which case the calling class field will be overwritten erratically).
SuperClass's supersStaticMethod cannot be Sealed, though the subclasses' versions can.
The "static variable" is cleared after SubClass.supersStaticMethod to ensure that this method is only ever called from a subclass.
I've added matlab.mixin.Heterogeneous as superclass of SuperClass for the purpose of demonstration.
classdef SuperClass < handle & matlab.mixin.Heterogeneous
properties (Abstract = true, Access = private, Constant = true)
subclass#meta.class scalar;
end
methods (Static, Abstract)
[arg1, arg2] = subsStaticMethod;
end
methods (Sealed, Static)
function out = currentClass(input) % < the closest thing in MATLAB to a static variable
persistent currentClass;
if nargout == 0 && nargin == 1 % "setter" mode
currentClass = input;
out = [];
else % "getter" mode
out = currentClass;
end
end
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
% Who am I?
whosCalling = SuperClass.currentClass();
if isempty(whosCalling) || ~isa(whosCalling,'meta.class')
[other_arg1,other_arg2] = deal(NaN);
return
else
whosCalling = whosCalling.Name;
end
fprintf(1,'\nCalled from: %s\n', whosCalling);
% Get data here
[arg1, arg2] = feval([whosCalling '.subsStaticMethod']);
% transform data here
other_arg1 = arg1+arg2; other_arg2=[arg1(:);arg2(:)];
fprintf(1,'other_arg1: %s, other_arg2: %s\n',...
num2str(other_arg1), mat2str(other_arg2));
% Clear the current class
SuperClass.currentClass([]);
end
end
end
classdef SubClass1 < SuperClass
properties (Constant)
subclass#meta.class scalar = ?SubClass1;
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
SubClass1.currentClass(SubClass1.subclass);
[other_arg1, other_arg2] = supersStaticMethod#SuperClass;
end
function [arg1, arg2] = subsStaticMethod
arg1 = -1; arg2 = -2;
end
end % static methods
end % classdef
classdef SubClass2 < SuperClass
properties (Constant)
subclass#meta.class scalar = ?SubClass2;
end
methods (Static)
function [other_arg1, other_arg2] = supersStaticMethod
SubClass1.currentClass(SubClass2.subclass);
[other_arg1, other_arg2] = supersStaticMethod#SuperClass;
end
function [arg1, arg2] = subsStaticMethod
arg1 = 1; arg2 = 2;
end
end % static methods
end % classdef
Then you can test it like this:
function q31269260
arr = [SubClass1, SubClass2];
for ind1 = 1:numel(arr)
arr(ind1).supersStaticMethod;
end
% arr.supersStaticMethod would not work because elements are treated as "instances" of
% SuperClass, whose supersStaticMethod should not be called directly.
The output is:
Called from: SubClass1
other_arg1: -3, other_arg2: [-1;-2]
Called from: SubClass2
other_arg1: 3, other_arg2: [1;2]
Related
New to Matlab but forced to use it for a new work project... I'm coming from a C++ background, but maybe I should not try to emulate coding paradigms from C++ with Matlab. I'm trying to figure out how to have a abstract class property that gets initialized when the constructor is called with a concrete member. For instance:
%AbstractClass.m
classdef (Abstract) AbstractClass < handle
end
%ConcreteClass.m
classdef ConcreteClass < AbstractClass
end
%myclass.m
classdef myclass < handle
properties
% X {AbstractClass} % would like to have this be like C++ nullptr
%X {AbstractClass} % can't initialize...
%X % Works, but not safe
X {mustBeA(X, 'AbstractClass')} % Suggested, error below
end
methods
% constructor
function obj = myclass( concreteInstanceOfAbstractClass )
obj.X = concreteInstanceOfAbstractClass
end
end
end
But when I try to run this
Y = ConcreteClass()
mustBeA(AbstractClass, Y) % no error, fine
mustBeA(ConcreteClass, Y) % no error, fine
A = myclass(Y) % throws error below
I get the following error
>> A = myclass(Y)
Error using implicit default value of property 'X' of class 'myclass'. Value must be one of the following types: 'AbstractClass'.
Is there a way to initialize the class in the constructor using the concrete instance of Abstract class? I have done some searching, but I'm new enough to Matlab that I'm probably not looking in the right places. This would be a very common thing to do in C++.
Try
properties
X {mustBeA(X, 'AbstractClass')}
end
which is a property validator rather than a class constraint.
Further to comments below, I realise the problem is that MATLAB insists on having a default value in the classdef. Unfortunately, this makes things difficult. One approach is to supply a valid concrete class as the default value. This works:
properties
X AbstractClass = ConcreteClass
end
but requires that the using class knows the name of ConcreteClass. Another approach (especially if the property needs to be set only at construction time) is this:
classdef Uses
properties (SetAccess = immutable)
% No constraints here
Property
end
methods
function obj = Uses(val)
arguments
% Enforce constraints here - scalar instance
% of something inheriting from AbstractClass
val (1,1) AbstractClass
end
obj.Property = val;
end
end
end
TL;DR
I am having an issue with the arguments functionality, wherein a subclass fails to instantiate if any name-value argument pairs are passed to it.
(Class definitions are given at the end.)
The arguments functionality, introduced in R2019b, brings great promise in terms of simplifying argument validation and removing boilerplate code from functions [1]. However, upon trying to implement name-value (NV) arguments taken from public class properties, I got the following error:
Invalid argument list. Check for wrong number of positional arguments or placement of positional arguments after
name-value pairs. Also, check for name-value pairs with invalid names or not specified in pairs.
before any subclass code is even executed. This message is confusing, because tab-completion appears to work as expected for NV pairs:
Moreover, if no arguments are passed at all, everything works fine:
>> FluidLayer()
ans =
FluidLayer with properties:
prop1: NaN
prop2: NaN
vs.
>> FluidLayer('prop1',1)
Error using FluidLayer
Invalid argument list. ...
Questions:
Why am I getting an error? I don't think I'm using the arguments mechanism in some unintended or undocumented way, so I'm doubly puzzled about what I might be doing wrong (assuming this is not a bug).
What can be done to resolve this, other than abandoning the whole arguments approach (I would like to keep argument name suggestions)? I have considered transitioning to varargin and/or using the functionSignatures.json approach - but these require significantly more work.
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = protected)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % methods
end
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
function layerObj = FluidLayer(props)
arguments
props.?FluidLayer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj#Layer(propsKV{:});
% Custom modifications:
end % constructor
end % methods
end
I have been able to simplify your example to this:
classdef Layer
properties (GetAccess = public, SetAccess = protected)
prop1
prop2
end % properties
methods
function layerObj = Layer(props)
arguments
props.?Layer
end
disp(props)
end % constructor
end % methods
end
Now Layer('prop1',1) throws an error as you describe.
Thus, it has nothing to do with subclassing or inheritance.
However, if we remove the SetAccess = protected restriction (leaving the properties with public get and set access), then it all works as you expected.
I don't know why restricting set access would limit this use case, as it has nothing to do with writing those properties, and a class method should have set access anyway. My guess is that this is a bug.
The documentation of R2020a states that the structName.?ClassName syntax can only be used with "settable properties defined by a class (that is, all properties with public SetAccess)". Thus, using it with protected properties is explicitly unsupported.
As such, if we want to use the "automatic" arguments validation mechanism, we have no choice but to set the SetAccess to public. However, this solution exposes the object properties to unwanted external changes, and so a workaround is suggested based on several principles:
properties now have public SetAccess, as required by the documentation.
Custom setters are added that perform access validation based on dbstack and meta.class comparison.
New Layer.m (notice the 2 new methods blocks):
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = public)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
%% Constructor
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % protected methods
%% Setters & Getters
methods
function set.prop1(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop1 = val;
else
Layer.throwUnprotectedAccess();
end
end
function set.prop2(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop2 = val;
else
Layer.throwUnprotectedAccess();
end
end
end % no-attribute methods
%% Pseudo-protected implementation
methods (Access = protected, Static = true)
function throwUnprotectedAccess()
stack = dbstack(1);
[~,setterName,~] = fileparts(stack(1).name);
throw(MException('Layer:unprotectedPropertyAccess',...
['Unable to set "', stack(1).name(numel(setterName)+2:end),...
'", as it is a protected property!']));
end
function mc = getCallerMetaclass()
stack = dbstack(2, '-completenames');
if isempty(stack)
mc = ?meta.class;
else
[~,className,~] = fileparts(stack(1).file);
mc = meta.class.fromName(className);
end
end
end % protected static methods
end % classdef
New FluidLayer.m (the foo method was added):
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
%% Constructor
function layerObj = FluidLayer(props)
arguments
props.?Layer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj#Layer(propsKV{:});
end % constructor
function obj = foo(obj)
obj.prop1 = obj.prop1 + 1;
end
end % methods
end % classdef
Here's a demonstration of how it works:
>> fl = FluidLayer('prop1', 2, 'prop2', 1)
fl =
FluidLayer with properties:
prop1: 2
prop2: 1
>> fl.prop1 = 5; % attempting to set property directly (unintended)
Error using Layer/throwUnprotectedAccess (line 51)
Unable to set "prop1", as it is a protected property!
Error in Layer/set.prop1 (line 32)
Layer.throwUnprotectedAccess();
>> fl.foo() % attempting to set property through method (intended)
ans =
FluidLayer with properties:
prop1: 3
prop2: 1
>>
In conclusion: it is possible to overcome the SetAccess = public limitation using setter methods, but convoluted modifications to class code are required.
Practical Notes:
The getCallerMetaclass is limited in that it doesn't correctly identify packages - resulting in potentially empty metaclass objects. So keep in mind that this function should be modified if that is the case.
dbstack is called multiple times, although it is not necessary (it can be called once in the setter and the result can then be passed around).
The setter code for different properties is 5-lines long and mostly replicated (with the exception of the property name) - this can be improved by grabbing the property name through dbstack.
I'm trying to implement a matlab class into a simulink MATLABSystem. When I implement superclass, the simulation works fine, but using the subclass I get the following error:
"Simulink cannot automatically infer the output signal properties of 'subclassSystem'. Simulink uses code generation technology to automatically determine the output signal properties from the System object. The System object 'subclass' contains code that does not support code generation.
The error is
'Undefined function or variable 'obj'. The first assignment to a local variable determines its class.'
The error occurred for MATLAB System block 'subclassSystem'. See line 29, column 28 in file 'superclass.m'. The error was detected during size propagation phase."
I commented this line in the code below
Do I have to specify something additional?
Here the subclass definition:
classdef subclass < superclass
properties(Access = protected) % These variables must be initialised. Here or in the setupImpl function
end
methods (Access=protected)
function resetImpl(obj)
end
%% Common functions
function setupImpl(obj)
% Perform one-time calculations, such as computing constants
setupImpl#superclass();
%obj.initFilter(obj.sampleTime, obj.delta_i, obj.delta_d, obj.g_mps2, obj.q0, obj.b_w0, obj.sigma_2_w, obj.sigma_2_a, obj.sigma_2_b_w, obj.sigma_2_yaw)
end
function attitude = stepImpl(obj,u, y)
% Implement algorithm.
attitude = 5;
end
end
methods
% Constructor must be empty for matlab.System. In Matlab call
% initFilter after the object was created. In simulink setupImpl()
% will be called
function obj = subclass()
obj#superclass();
end
end
end
Here the superclass definition:
classdef superclass < matlab.System
% These variables must be initialized in the simulink model
properties
sigma_2_w;
sigma_2_a;
sigma_2_b_w;
sigma_2_yaw;
end
properties(Access = protected) % These variables must be initialised. Here or in the setupImpl function
R;
Q;
end
methods (Access = protected)
function resetImpl(obj)
end
%% Common functions
function setupImpl(obj)
% Perform one-time calculations, such as computing constants
obj.Q = diag([obj.sigma_2_w',obj.sigma_2_b_w']); % this is line 29
obj.R = diag([obj.sigma_2_a',obj.sigma_2_yaw']);
end
function attitude = stepImpl(obj,u, y)
% Implement algorithm.
attitude = 5;
end
end
methods
% Constructor must be empty for matlab.System. In Matlab call
% initFilter after the object was created. In simulink setupImpl()
% will be called
function obj = superclass()
% Support name-value pair arguments when constructing object
end
end
end
I found the error. In the setupImpl function of the subclass I had to call setupImpl of the superclass with the obj as argument:
setupImpl#Superclass(obj);
This is my constructor. Here I didn't use the obj as return value
function obj = Subclass()
obj = obj#Superclass();
end
I have created some MATLAB classes to do some error-checking when I use certain types of structures. This improve development time by preventing errors in the code, but significantly slow down execution time.
One way to get around this is to comment out the set methods inside the class. Is it possible to do this programmatically? For example, only define these methods if a parameter in the constructor is true.
classdef MWE
%MWE Minimum working example
properties
A
B
C
end
methods
function obj = MWE(A, B, C)
if nargin ~= 3
error('A, B and C must all be provided.');
end
obj.A = A;
obj.B = B;
obj.C = C;
end
% function obj = set.A(obj, value)
% validate(obj, value, 'A');
% obj.A = value;
% end
%
% function obj = set.B(obj, value)
% validate(obj, value, 'B');
% obj.B = value;
% end
%
% function obj = set.C(obj, value)
% validate(obj, value, 'C');
% obj.C = value;
% end
end
methods (Access = private)
function validate(obj, value, name)
% Code here
end
end
end
Is it possible to do this programmatically? For example, only define these methods if a parameter in the constructor is true.
After some discussion, I see there are different ways of looking at your question. And, indeed, it may be that you cannot do what you are asking as interpreted by some. Here are two cases, however, that may suffice.
Case 1
You have computationally intensive or otherwise time consuming methods, that you use to "do some error-checking", in a development setting, but want to turn off in a production environment. And, these checks occur when the class is instantiated.
Place these methods in a wrapper function that is called from the constructor.
Here's an example
classdef classFoo
properties(Access=private)
fbar;
end
methods
function this = classFoo(arg1, argN, debugMode)
if(nargin>2 && debugMode)
if(~this.verifyStuff(arg1, argN))
throw(MException('classFoo:ConstructFailure','Could not verify'));
else
this.fbar = timeConsumingFunction();
end
else
this.fbar = 42; % defaultValue;
end
% continue construction
end
function didVerify = verifyStuff(this, varargin)
% complex code
didVerify = randi(2)-1; % 50/50
end
end
end
Then when creating objects you can choose to pass the debug mode flag as true like this:
a=classFoo(1,2,true)
or as false, like this:
a=classFoo(1,2,false)
or not at all, which is the same as the false case, like this:
a=classFoo(1,2)
Case 2
You have two different versions of a get/set method that you are commenting out depending on your development environment.
Add a private member parameter (e.g. isDebugging) and set it at time of construction. Now, instead of commenting out code in your get and set methods, you can handle the different cases with a simple if/else, predicated on your debug state like this:
classdef classFoo
properties(Access=private)
fbar;
isDebugging;
end
methods
function this = classFoo(debugMode)
if(nargin<1 || ~islogical(debugMode))
debugMode = false;
end
this.isDebugging = debugMode;
end
function setfbar(this, fIn)
if(this.isDebugging)
this.fbar = timeConsumingFunction(fIn);
else
this.fbar = fIn; % defaultValue;
end
end
end
end
I'm new to MATLAB, and I want to write a method of a class that change the property of this object:
classdef Foo
properties
a = 6;
end
methods
function obj = Foo()
end
function change(obj,bar)
obj.a = bar
end
end
end
foo = Foo()
foo.change(7) //here I am trying to change the property to 7
It turns out the property is still 6.
MATLAB makes a difference between value classes and handle classes. Instances of value classes are implicitely copied on assignments (and hence behave like ordinary MATLAB matrices), instances of handle classes are not (and hence behave like instances in other OOP languages).
Therefore, you have to return the modified object for value classes:
classdef ValueClass
properties
a = 6;
end
methods
function this = change(this, v)
this.a = v;
end
end
end
Call it like this:
value = ValueClass();
value = value.change(23);
value.a
Alternatively, you can derive your class from the handle class:
classdef HandleClass < handle
properties
a = 6;
end
methods
function change(this, v)
this.a = v;
end
end
end
And call it like this:
h = HandleClass();
h.change(23);
h.a
There's more information in the MATLAB documentation.