Class property validation failure due to implicit instantiation defaults.

조회 수: 20(최근 30일)
Say I have
classdef MyClass
properties
a (1, 1) {mustBePositive}
end
methods
function obj = MyClass(a)
obj.a = a;
end
end
end
which will produce the following error "Value must be positive" upon running
MyClass(1)
which is ridiculous since 1 is clearly positive.
Something about Matlab initializing the class instance with implicit default 0 I think? I don't understand why Matlab doesn't just allocate space dependent on the conditions provided (e.g. "mustBePositive") like other programming languages do.
So, time to fix it. If I change my class to
classdef MyClass
properties
a (1, 1) {mustBePositive} = []
end
methods
function obj = MyClass(a)
obj.a = a;
end
end
end
then the property validation fails because it must be a scalar. I'm considering writing my own "mustBePositive" function, but someone somewhere is going to tell me off for bad programming practice, of which I agree.
If I instead change my class to
classdef MyClass
properties
a (1, 1) {mustBePositive} = 1
end
methods
function obj = MyClass(a)
obj.a = a;
end
end
end
then sure, it doesn't throw an error. But this, I would argue, is bad implementation practice, especially in the context that I'm applying Matlab classes for (which I won't go into). If I am to have a default value, then it must be acceptably applicable in the contexts of usage, which it is not.
How can I deal with this property validation failure for implicit defaults?
  댓글 수: 1
per isakson
per isakson 2022년 1월 21일
편집: per isakson 2022년 1월 21일
Yes, that's the way Matlab works.
The documentations, Validate Property Values, says:
Specify Valid Default
Ensure that any default value [including implicit defaults] assigned to the property meets the restrictions imposed by the specified validation.
I don't think there is a better way than explicitely assigning a valid default value.

댓글을 달려면 로그인하십시오.

답변(2개)

Lawrence
Lawrence 2022년 4월 19일
편집: Lawrence 2022년 4월 19일
You can hack your way around this requirement by making the verified property dependent and having a private unverified implementation.
classdef MyClass
properties (Dependent = true)
a {mustBePositive}
end
properties (Access = private)
a_impl
end
methods
function obj = MyClass(a)
obj.a = a;
end
function obj = set.a(obj,new_a)
obj.a_impl = new_a;
end
function a = get.a(obj)
a = obj.a_impl;
end
end
end
With dependent values there are no default values assigned, so you don't get the pre-constructor initialization error. The validation still is applied when you try to set obj.a, and the unqualified use of the "a" input in the MyClass constructor ensures that it will be passed and the initial value will be verified. Note that this class is still not default-constructable, since I haven't provided a default value of the "a" input into the MyClass constructor.
  댓글 수: 3
Lawrence
Lawrence 2022년 4월 21일
편집: Lawrence 2022년 4월 21일
If you're willing to dive deep into hacky solutions to simplify the work of implementing this for many properties, you can try overloading subsasgn to interrupt assignement to the dependent properties. Then you can do the redirect to private struct and/or private implementations, making use of dynamic field references https://www.mathworks.com/help/matlab/matlab_prog/generate-field-names-from-variables.html. Note that this bypasses any property block function argument validation for the dependent properties. So, you need to call the validation functions manually in subsasgn before assigning into the private struct.
Personally, around this point I myself would just throw in the towel and supply valid default values.

댓글을 달려면 로그인하십시오.


Lawrence
Lawrence 2022년 4월 21일
편집: Lawrence 2022년 4월 21일
I read your comment about needing to do this for many properties. After doing a little more digging, I learned that you can get the meta.Validation information for any property and call that at will, which lets you combine overloading subsasgn with typical properties-block validation specification. This lets you meta-program a way to simply specify the dependent properties. It's arguably even more of a hack than the previous answer I gave above, but at least it's using the facilities that MATLAB designed for this type of hacking.
This answer also properly errors if you try to access an uninitialized variable, rather than return the empty double array that the private implementation in my previous answer would return regardless of if an empty double passes validation. (I suppose, though, that in the previous version you could use a struct and verify the field exists or use metaprogramming to validate on get as well to restore this behavior. Perhaps this would even be desirable, since another programmer could come along and monkey with the private implementations and break the validation you have specified.)
One downside of this method that you have no get and set methods for the dependent properties, so you cannot see them when displayed in the command window or interact with them in the variable editor panel. You can fix command window display with https://www.mathworks.com/help/matlab/matlab_oop/implementing-a-custom-display.html, but I'm not sure yet how to fix the variable editor.
Another downside is that subsasgn isn't called automatically inside the constructor, so you have to either call it manually (to get the validation) or set to the DependentDataStore directly (simpler but no validation).
classdef MyClass
properties (Dependent = true)
a {mustBePositive}
b {mustBeTextScalar}
end
properties (Access = private)
DependentDataStore (1,1) struct = struct
end
methods
function this = h(a,b)
%Either this (the "proper" way):
this = subsasgn(this,struct('type','.','subs','a'),a);
this = subsasgn(this,struct('type','.','subs','b'),b);
%Or this (simpler but with no validation):
this.DependentDataStore.a = a;
this.DependentDataStore.b = b;
%But not this (errors b/c there are no set methods):
% this.a = a;
% this.b = b;
end
function B = subsref(A,S)
meta = getPropertyMeta(A,S);
if meta.Dependent
if ~isfield(A.DependentDataStore,S(1).subs)
ME = MException('DependentProperty:UninitializedUse','The value of property %s has not been initialized and therefore cannot be read.',S(1).subs);
throwAsCaller(ME);
end
if numel(S) == 1
B = A.DependentDataStore.(S(1).subs);
else
B = subsref(A.DependentDataStore.(S(1).subs),S(2:end));
end
else
B = builtin('subsref',A,S);
end
end
function A = subsasgn(A,S,B)
meta = getPropertyMeta(A,S);
if meta.Dependent
newVal = calculateValueAfterAssignment(A,S,B);
try
newVal = validateNewVal(meta,newVal);
catch ME
throwAsCaller(ME)
end
A.DependentDataStore.(S(1).subs) = newVal;
else
A = builtin('subsasgn',A,S,B);
end
end
end
end
function meta = getPropertyMeta(A,S)
meta = metaclass(A);
meta = meta.PropertyList(strcmp({meta.PropertyList.Name},S(1).subs));
end
function newVal = validateNewVal(meta,newVal)
if ~isempty(meta.Validation)
newVal = meta.Validation.validateValue(newVal);
end
end
function newVal = calculateValueAfterAssignment(A,S,B)
if numel(S) == 1
newVal = B;
else
if ~isfield(A.DependentDataStore,S(1).subs)
currentVal = [];
else
currentVal = A.DependentDataStore.(S(1).subs);
end
newVal = subsasgn(currentVal,S(2:end),B);
end
end
And example usage:
>> obj = MyClass(2,'3')
obj =
MyClass with no properties.
>> obj.a = 3
obj =
MyClass with no properties.
>> obj.a
ans =
3
>> obj.a = -2
Value must be positive.
>> obj.b = 'qwer';
>> obj.b
ans =
'qwer'
>> obj.b(2) = 'a'
H =
MyClass with no properties.
>> obj.b
ans =
'qaer'
  댓글 수: 1
Aaron Kaw
Aaron Kaw 2022년 5월 10일
Thank you so much!
This has become so much more of a hassle than is really needed, and I blame the design of MATLAB. This is one of many issues that I have with MATLAB.
Other languages you simply designate a default value, and you don't go through all this nonsense.
If I ever really REALLY need to implement this, then I'll take a closer look.
Again, thank you for showing me how this could all be done.

댓글을 달려면 로그인하십시오.

범주

Find more on Environment and Settings in Help Center and File Exchange

제품


릴리스

R2021b

Community Treasure Hunt

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

Start Hunting!

Translated by