Developer Zone

Advanced Software Development with MATLAB

The Gift of Property Cheer 4

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



classdef HyperlinkDecorator
   
    properties
        LinkTarget
    end
    
    methods
        function decorator = HyperlinkDecorator(linkTarget)
            decorator.LinkTarget = linkTarget;
        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;

% Add a link to US Holidays
holidayWikipediaSite = 'https://en.wikipedia.org/wiki/List_of_multinational_festivals_and_holidays#December';
greeting(2).Decorator = HyperlinkDecorator(holidayWikipediaSite);

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
        Ramadan
        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).Decorator = HyperlinkDecorator(holidayWikipediaSite);
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 Ramadan !
Happy WinterSolstice !
Happy NewYears !


Get the MATLAB code

Published with MATLAB® R2017b

4 CommentsOldest to Newest

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?

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.

Add A Comment

Your email address will not be published. Required fields are marked *

Preview: hide