If you have used OOP to create your own Matlab classes in the past, you are most likely familiar with the different attributes that can be set to the classdef
, properties
, methods
and events
blocks. And while the documentation describes in detail the attributes that can be set in all classes (for example, the methods Access, Hidden or Sealed attributes), Matlab reserves certain attributes to some framework-specific classes.
Classes that use certain framework base classes have framework-specific attributes. See the documentation for the specific base class you are using for information on these attributes.
Method attributes
And that’s pretty cool, because that opens the door to us potentially defining our own attributes. The goal of todays post is to see if that is actually possible in Matlab, and how would we implement those custom attributes.
Base classes with their own attributes
My first idea for investigating how would we create a custom attribute was looking into which base classes (classes that ship with Matlab) have custom attributes defined. There’s actually very few of them:
Base class | Attributes | |
---|---|---|
matlab.unittest.TestCase | Defines a lot of attributes for the class (TestTags, SharedTestFixtures), properties (TestParameter, MethodSetupParameter, ClassSetupParameter) and methods (Test, TestMethodSetup, TestMethodTeardown, …). | |
matlab.ui.componentcontainer.ComponentContainer and matlab.graphics.chartcontainer.ChartContainer | Define the HasCallbackProperty event attribute Also the undocumented property attributes UsedInUpdate and Resettable (I have no idea what these are used for) | |
matlab.io.internal.FunctionInterface (undocumented) | Defines the Provider and Parameter attributes (I also don’t know their effects) | |
parallel.internal.customattr.CustomPropTypes (undocumented) | Defines the PCTGetSet and PCTConstraints attributes (unknown use) |
The HasCallbackProperty attribute of the ComponentContainer
is what actually triggered my research into defining custom attributes. It’s behaviour is like this: if an events block contains this attribute, the class will automatically create an additional user-settable property for every event in the block. This property will contain a function, and it will be executed every time an event triggers (through the notify
method). That, in my opinion, is very neat. By defining the event we are also creating a callback function (a listener, actually) without the need of writing the code ourselves.
The above classes are able to modify their metadata, extending the properties of meta.class
, meta.property
, meta.method
and meta.event
abstract classes with their new attributes.
The problem, as you might already imagine, is that these classes are either built-in or p-coded. That means that, sadly, we cannot access their source code to dive into the mechanisms with which they define the custom attributes. But not all hope is lost!
Defining our own attributes
There are 2 semi-documented attributes that apply to the classdef, properties, methods & events blocks that you might not be aware of: the Description
and DetailedDescription
.
These attributes get filled by the comments that we add to the individual elements (each property, method or event) inside the block. For example, if we check the metadata of MyProperty
in the class below, we will find that the comment 2 lines above the property is the description, and the one 1 line above it is the detailed description:
classdef CustomClass < handle
properties
% MYPROPERTY is just a demo property
% should be a double
MyProperty
end
end
property with properties:
Description: 'MYPROPERTY is just a demo property'
DetailedDescription: 'should be a double'
...
But we can also define the Description
attribute in the properties block! We must, however, take into account that this will alter how the help information of the class will be displayed.
classdef CustomAttributeClass < handle
properties (Description = "CustomAttribute")
% MYPROPERTY is just a demo property
% should be a double
MyProperty
end
end
property with properties:
Description: 'CustomAttribute'
DetailedDescription: 'should be a double'
...
Quick note: The position of the comments that affect the Description
is different for methods, properties and events. A good practice is to test how the Description
attribute impacts the documentation, and come up with ways in which all the comments go into the DetailedDescription
.
Parsing attributes inside a no-arguments constructor
The idea is to have a superclass that defines a method that can parse the meta-properties of the block elements, look for those whose description matches our attribute, and then add the custom functionality.
The best method in which to do that is no other than the constructor of the superclass. This is because it will always be called (explicitly or implicitly) from the subclass at creation, and it will anyways have access to the subclass metadata.
If you do not explicitly call the superclass constructors from the subclass constructor, MATLAB implicitly calls these constructors with no arguments.
Design Subclass Constructors
This is how our superclass would look like for a class that implement a CustomAttribute
property attribute:
classdef (Abstract) DefinesCustomAttribute < handle
methods
function this = DefinesCustomAttribute()
% no argument constructor
% get the metadata of the subclass
mc = meta.class.fromName(class(this));
% get the properties
props = mc.PropertyList;
% find the properties whose description mathches "MyAttribute"
idx = strcmp({props.Description}, "CustomAttribute")]);
attrProps = props(idx);
% add the custom behaviour for this properties
for ii = 1:numel(attrProps)
...
end
end
end
end
The nicest thing is that (counterintuitively?) class(this)
will not return “DefinesCustomAttribute”, but name of the subclass where the constructor is being called instead. That’s what allows us to read all the subclass properties and finding the ones with the custom attribute.
Any class inheriting from DefinesCustomAttribute
can now specify the custom property attribute and expect some side-effect from it, like the CustomAttributeClass
we defined earlier.
Implementing the HasCallbackProperty
With all this new knowledge, let’s try implementing a superclass that allows us to generate an effect identical to the HasCallbackProperty attribute in the ComponentContainer
class.
The HasCallbackProperty attribute creates a new property, and we will need to do that at runtime inside the constructor of our superclass. The only way to do that is inheriting from the dynamicprops
class.
classdef (Abstract) HasCallbackPropertyMixin < dynamicprops
properties (Access = private, Hidden)
EventListeners
end
methods
function this = HasCallbackPropertyMixin()
% SAME AS BEFORE
% get the metadata of the subclass
mc = meta.class.fromName(class(this));
% get the events
evts = mc.EventList;
% find the events whose description mathches "HasCallbackProperty"
idx = strcmp({evts.Description}, "HasCallbackProperty")]);
cbEvts = props(idx);
% CUSTOM BEHAVIOUR: Add a property for each event
for ii = 1:numel(cbEvts)
p = this.addprop(cbEvts(ii).Name + "Fcn");
p.SetMethod = setProp(this, p.Name);
this.(p.Name) = @(~,~) nop(); % Default does nothing
% Whenever the event is notified, evaluate the callback property
this.EventListeners(end + 1) = addlistener(this, cbEvents(ii).Name, ...
@(src,evt) feval(this.(p.Name), src, evt));
end
end
end
end
function setProp(this, value)
this.(prop) = value;
end
function nop()
end
Now any class inheriting from HasCallbackPropertyMixin
will be able to create events that autogenerate callback functions that trigger when the event is notified.
Conclusion
In reality, custom attributes are just nice-to-haves. Most of the times they aren’t really needed. For example, in the HasCallbackPropertyMixin
class we could have moved the custom behaviour logic to a protected method, and make subclasses responsible for calling it during their constructor and with the events required:
classdef HasCallbackPropertyMixin < dynamicprops
methods (Access = protected)
function addDynamicPropsToEvents(this, eventNames)
...
end
end
end
The beauty of custom attributes, however, is that they simplify the way in which we write code: instead of having to remember to call the addDynamicPropsToEvents method in a subclass constructor, we can just tag the events with the HasCallbackProperty description.
And creating them, as we have seen, is not difficult. But it can become a bit dirty. Specially since we are unintentionally breaking the description (and therefore the Matlab documentation feature) of the elements of our class.
I guess the conclusion is… use them, but don’t overuse them.
One last note
The Component
class in Weblab defines 2 custom attributes: HasCallbackProperty and CanBeStained. CanBeStained works together with SetObservable such that, after setting the property, a function is executed. Check the code if you want to learn more. As always, comments are appreciated.