## Developer ZoneAdvanced Software Development with MATLAB

### This is machine translation

Translated by
Mouseover text to see original. Click the button below to return to the English version of the page.

# The Gift of Property Cheer4

Posted by Andy Campbell,

Well, it's that time of year, Christmas, Hanukkah, Kwanzaa, Ramadan, Winter Solstice, or you know.......Friday. You deserve the gift of a blog post. Here's a nugget of object oriented goodness in MATLAB, what better gift could there be?

Well perhaps a quick follow up discussion on some more benefits of MATLAB object properties as we have discussed a couple times before. This time I'd like to focus on an awesome (and relatively new) feature that allows for much more streamlined validation of properties.

First, a quick reminder, take a look at this special string class called FancyString that enables decorating individual elements it the string array with additional information such as hyperlinking or bolding. This starts with the FancyString itself:

classdef FancyString

properties
PlainValue

% Each element uses the "do nothing" decorator by default
Decorator = DefaultDecorator;
end

properties(Dependent)
DecoratedValue
end

methods
function fancyString = FancyString(varargin)
if nargin < 1
return
end

plainValue = string(varargin{:});
fancyString(numel(plainValue)) = FancyString;

plainValueCell = num2cell(plainValue);
[fancyString.PlainValue] = plainValueCell{:};
end

function decorated = get.DecoratedValue(fancyString)
% The decorated value returns whatever the decorator adds to
% the plain string.
decorated = fancyString.Decorator.decorate(fancyString.PlainValue);
end
function s = string(fancyString)
s = reshape([fancyString.PlainValue], size(fancyString));
end
end
end



As well as a couple decorators to add some fanciness:

classdef DefaultDecorator

methods
function decoratedString = decorate(~, undecoratedString)
decoratedString =  undecoratedString;
end
end

end

properties
end

methods
end
function decoratedString = decorate(decorator, undecoratedString)
decoratedString = "<a href=""" + decorator.LinkTarget + """>" + undecoratedString + "</a>";
end
end

end

classdef BoldDecorator

methods
function decoratedString = decorate(~, undecoratedString)
decoratedString = "<strong>" + undecoratedString + "</strong>";
end
end

end



Now we can have a nice message with some bolding and hyperlinking holiday cheer:

holidayMessage = ["Happy" "Holidays" "!"];

greeting = FancyString(holidayMessage);
greeting(1).Decorator = BoldDecorator;

holidayWikipediaSite = 'https://en.wikipedia.org/wiki/List_of_multinational_festivals_and_holidays#December';

join([greeting.DecoratedValue])

ans =

"Happy Holidays !"



Alright this is great, but there is a problem. Let's say we have an enumeration containing the different holidays and we'd like to put together a few different sets of greetings for our friends and neighbors with different preferences. Maybe this looks something like this:

classdef Holidays
% Simple (and incomplete) enumeration of some winter holidays

enumeration
Christmas
Hanukkah
Kwanzaa
WinterSolstice
NewYears
end
end



That looks so nice I might just want to replace my string with one of these enumeration values. Seems reasonable.

greeting(2).PlainValue = Holidays.Kwanzaa

greeting =

1×3 FancyString array with properties:

PlainValue
Decorator
DecoratedValue



So far so good. Quick spot check?

greeting.PlainValue

ans =

"Happy"

ans =

Kwanzaa

ans =

"!"



Still looks good at first glance. Alright how about the DecoratedValue? Do the hyperlinking and bold operations still work? (hint: the try-catch I've added in the script I am using to publish this blog is a hint that something fishy might be going on):

try
greeting.DecoratedValue
catch e
disp('Errored!');
disp(e.getReport('basic'));
end

Errored!
Undefined function 'plus' for input arguments of type 'Holidays'.


Alright this doesn't work because I've assigned to the PlainValue a value from our enumeration, but the FancyString (and the decorators) are expecting the value to be a string.

In fact, we could have supplied any arbitrary value to the property and we likely wouldn't have noticed until much farther down the line. For example, let's add a structure, which is meaningless in this context:

meaninglessStruct.SomeField = 5;
meaninglessStruct.AnotherField = true;

greeting(2).PlainValue = meaninglessStruct; % Assignment works!!
greeting.PlainValue % Still doesn't error!

ans =

"Happy"

ans =

struct with fields:

SomeField: 5
AnotherField: 1

ans =

"!"



It only errors when we use it farther down the line, such as in concatenation:

try
[greeting.PlainValue]
catch e
disp('Errored!');
disp(e.getReport('basic'));
end

Errored!
Error using string
Conversion to string from struct is not possible.


This is clearly not optimal. Property validation to the rescue! We can make the simplest of tweaks to the FancyString so that we can always know for sure that the PlainValue property will always be a string. It's as easy as this slightly more fancy string implementation:

classdef MoreFancyString

properties

% Property validation for the win! Let's guarantee the PlainValue
% is always a string
PlainValue (1,1) string

Decorator = DefaultDecorator;
end

properties(Dependent)
DecoratedValue
end

methods
function fancyString = MoreFancyString(varargin)
if nargin < 1
return
end

plainValue = string(varargin{:});
fancyString(numel(plainValue)) = MoreFancyString;

plainValueCell = num2cell(plainValue);
[fancyString.PlainValue] = plainValueCell{:};
end

function decorated = get.DecoratedValue(fancyString)
% The decorated value returns whatever the decorator adds to
% the plain string.
decorated = fancyString.Decorator.decorate(fancyString.PlainValue);
end
function s = string(fancyString)
s = reshape([fancyString.PlainValue], size(fancyString));
end
end
end



The only difference here is in the property block where plain value was defined:

PlainValue (1,1) string

This states that the PlainValue must always be a scalar string, which gives us much more peace of mind as we process that code. How does it work now?

greeting = MoreFancyString(holidayMessage);
greeting(1).Decorator = BoldDecorator;
greeting(2).PlainValue = Holidays.Kwanzaa

greeting =

1×3 MoreFancyString array with properties:

PlainValue
Decorator
DecoratedValue



This time if you look closely you will see the enum was converted to a string

greeting(2).PlainValue

ans =

"Kwanzaa"



...and because of that everything else like concatenation and the string decoration works as expected:

join([greeting.PlainValue])

join([greeting.DecoratedValue])

ans =

"Happy Kwanzaa !"

ans =

"Happy Kwanzaa !"



It also correctly errors right away with bunk values that don't know how to be represented as strings:

try
greeting(2).PlainValue = meaninglessStruct;
catch e
disp('Errored!');
disp(e.getReport('basic'));
end

Errored!
Error using y2017PropertyTypes (line 138)
Error setting property 'PlainValue' of class 'MoreFancyString':
Invalid data type. Value must be string or be convertible to string.


Awesome! A couple concluding thoughts:

1. We could have also given this guarantee by implementing a set.PlainValue method that validates the input using the validateattributes or otherwise. This would be certainly possible, and a benefit of encapsulated properties. However, if this validation is all that is needed property validators are much more convenient and accessible.
2. Classes that are convertible to the datatype specified (in this case string) are automatically converted. In this case the enumeration really knows how to be interpreted correctly as a string, and so it just intuitively worked out of the box!
3. This conversion has a flipside. What this really provides is a guarantee that the actual stored value will be a string. However,you can set non-string values to this property as long as they are convertible to a string, and the conversion will be performed upon setting the property. This is typically the right behavior, since the value with a type conversion is the class that should be able to decide that it can be interpeted as a string. However, if you find that you wish to disallow any datatype from being assigned to the property unless it is the precise type to start with, then you may need to leverage the setter method instead to enforce that restriction directly.

Have you used the new property validation features? I hope so. Actually there is much more richness to property validation than I've shown here, check out the documentation. I've hinted at size/shape validation but there is also the ability to pass validation functons to the value being set, as well as a rich library of validation functions in MATLAB for your benefit. Let us know what you think!....oh, and

holidays = enumeration('Holidays');
for idx = 1:numel(holidays)
greeting(2).PlainValue = holidays(idx);
if strcmp(holidays(idx), 'Christmas') % There's always a special case
specialGreeting = greeting;
specialGreeting(1).PlainValue = "Merry";
disp(join([specialGreeting.DecoratedValue]));
continue;
end
disp(join([greeting.DecoratedValue]));
end


Merry Christmas !
Happy Hanukkah !
Happy Kwanzaa !
Happy WinterSolstice !
Happy NewYears !


Get the MATLAB code

Published with MATLAB® R2017b

David Barry replied on : 1 of 4
Looking forward to trying out this new feature when we upgrade in the new year. Thanks for pointing out the data type conversion gotcha as that definitely would have tripped me up. It would be interesting to know why this design decision was made. I can’t think of a usecase in my own code where I would want anything other than error if I wasn’t setting a string. I guess I’ll have to stick to setters for now. Maybe some optional setting could be added to the property def in the future to turn off data type conversion and force strict type matches?
Andy Campbell replied on : 2 of 4
Hey David, The way I think about it is that it is actually the string class who has given other types the ability to be convertible to it. As such, it has given the rest of MATLAB the ability to be substitutable for anything that expects and requires string. I probably wouldn't be the right person to delve into the specifics as to why the string class has chosen this behavior, but I do know that there has been a lot of analysis about the string class and the different usage models and it no doubt is a result of making sure this behavior supports all of the ways that people would like to use strings (one quick example, it enables great operations like:
 >> "file" + [1:10] + ".txt"

ans =

1×10 string array

"file1.txt"    "file2.txt"    "file3.txt"    "file4.txt"    "file5.txt"    "file6.txt"    "file7.txt"    "file8.txt"    "file9.txt"    "file10.txt""file" + [1:10] + ".txt"
Anyway, the point is that the string class has enabled this behavior, for perhaps a multitude of reasons, but it has the right to define this behavior against its interface. Another class that reuses string has to work harder, perhaps for good reason, to "deny" the string class this aspect of its interface. Note that for your own class, the behavior you expected will occur unless your own class has made itself convertible into another type. Thus this aspect of the property validation works for both sets and gets by default. It just requires that you are aware of the contract of your collaborators, in this case string. I think that your suggestion of a property attribute, such as
 properties(DisableAutoConversion)
or something like that has some merit and I will create an enhancement request to that effect. In the meantime you certainly can still leverage a setter method to validate the type explicitly...but I would actually then suggest only adding the specific class check and rely on the property validator for other aspects such as size or other properties. This would set you up more nicely for the future I predict. Hope that helps! Andy
David Barry replied on : 3 of 4
Perfect, thanks for the reply and tips Andy. Oh by the way, the spam protection sum checker kept failing on me the other day. I only managed to submit the post by entering a random number (not the answer to the sum shown).
Andy Campbell replied on : 4 of 4
Hmm, interesting on the spam protection math problem. I'll let the right team know about it and pass on any tips that I may have. In the meantime, let me know if it continues to happen.

This site uses Akismet to reduce spam. Learn how your comment data is processed.