Search code examples
functionmatlaboopstaticprivate

Need class to expose a private static function via handle


I need a particular class to expose a private static function. I do this via an anonymous function stored in a cell array. The contrived example below illustrates the issue:

classdef ParamClass
    properties (Constant)
        param_spec = {
            {'param_1', @(x) isnumeric(x) && isscalar(x) && (x > 0)}, ...
            {'param_2', @(x) ParamClass.validate_param_2(x)} ...
            };
    end
    
    properties
        param_1;
        param_2;
    end
    
    methods (Static, Access = 'private')
        function ok = validate_param_2(x)
            ok = isnumeric(x) && isscalar(x) && (x  < 0);
        end 
    end
    
    methods
        function obj = ParamClass(p1, p2)
            opts = parse_my_args(p1, p2, ParamClass.param_spec{:});
            obj.param_1 = opts.param_1;
            obj.param_2 = opts.param_2;
        end
    end
end

function opts = parse_my_args(p1, p2, varargin)
    assert(varargin{1}{2}(p1));
    assert(varargin{2}{2}(p2));
    
    opts.param_1 = p1;
    opts.param_2 = p2;
end

The function ParamClass.validate_param_2 is private static, but I want it to be called by an outside standalone function via the anonymous function wrapper. However I get the following error:

Error using ParamClass.validate_param_2

Cannot access method 'validate_param_2' in class 'ParamClass'.

After some thought, the error seems unnecessary. The class passes a function handle to the outside. The contents of the handle should be a matter only for the class to decide and should not be anyone's business. Should not all the required context for validate_param_2 be part of the anonymous function? Of course I can remove the error by making validate_param_2 public, but I would like to understand the rationale behind the error.

Any comments explaining why this kind of error is needed would be appreciated. Also any alternatives?


Solution

  • This is indeed a strange error, and I don't think it makes much sense. One explanation could be that while the property constraints are being evaluated, the class definition is not yet complete in memory, and the function ParamClass.validate_param_2() is not yet available. But again, there is no reason for it to be this way, it might not have been a conscious choice (i.e. it's a bug).

    As an alternative, you could instead use a local function (i.e. a function defined after the classdef block, in the same file). Such a function is visible only from within the file, but you can return a handle to it, to make it useable from another file. This local function also has access to private members of the class. For all intents and purposes it is a private member, except it's only visible from within the file—class methods defined in the @ParamClass directory will not be able to see it.

    classdef ParamClass
        properties (Constant)
            param_spec = {
                {'param_1', @(x) isnumeric(x) && isscalar(x) && (x > 0)}, ...
                {'param_2', @validate_param_2} ...
                };
        end
        
        properties
            param_1;
            param_2;
        end
        
        methods
            function obj = ParamClass(p1, p2)
                opts = parse_my_args(p1, p2, ParamClass.param_spec{:});
                obj.param_1 = opts.param_1;
                obj.param_2 = opts.param_2;
            end
        end
    end
    
    function opts = parse_my_args(p1, p2, varargin)
        assert(varargin{1}{2}(p1));
        assert(varargin{2}{2}(p2));
        
        opts.param_1 = p1;
        opts.param_2 = p2;
    end
    
    function ok = validate_param_2(x)
        ok = isnumeric(x) && isscalar(x) && (x < 0);
    end